You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by ch...@apache.org on 2009/02/15 18:35:07 UTC

svn commit: r744702 [1/3] - in /incubator/shindig/trunk/php: ./ config/ src/common/ src/gadgets/ src/gadgets/render/ src/gadgets/rewrite/ src/gadgets/servlet/ test/misc/

Author: chabotc
Date: Sun Feb 15 17:35:05 2009
New Revision: 744702

URL: http://svn.apache.org/viewvc?rev=744702&view=rev
Log:
This commit starts the refactoring of the gadget rendering implementation to accomedate for proxied content, data pipeling and a unified dom tree parser for rewriting, sanatizer and templating. This should be considered *unstable and incomplete* and not used in production.

Added:
    incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php
    incubator/shindig/trunk/php/src/gadgets/GadgetSpec.php
    incubator/shindig/trunk/php/src/gadgets/MakeRequestHandler.php
    incubator/shindig/trunk/php/src/gadgets/ProxyBase.php
    incubator/shindig/trunk/php/src/gadgets/render/
    incubator/shindig/trunk/php/src/gadgets/render/GadgetHrefRenderer.php
    incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php
    incubator/shindig/trunk/php/src/gadgets/render/GadgetRenderer.php
    incubator/shindig/trunk/php/src/gadgets/render/GadgetUrlRenderer.php
    incubator/shindig/trunk/php/src/gadgets/servlet/MakeRequestServlet.php
Removed:
    incubator/shindig/trunk/php/src/gadgets/Auth.php
    incubator/shindig/trunk/php/src/gadgets/FeatureSpec.php
    incubator/shindig/trunk/php/src/gadgets/GadgetException.php
    incubator/shindig/trunk/php/src/gadgets/GadgetFeature.php
    incubator/shindig/trunk/php/src/gadgets/GadgetFeatureFactory.php
    incubator/shindig/trunk/php/src/gadgets/GadgetId.php
    incubator/shindig/trunk/php/src/gadgets/GadgetServer.php
    incubator/shindig/trunk/php/src/gadgets/GadgetSigner.php
    incubator/shindig/trunk/php/src/gadgets/GadgetSpecFactory.php
    incubator/shindig/trunk/php/src/gadgets/HttpUtil.php
    incubator/shindig/trunk/php/src/gadgets/Icon.php
    incubator/shindig/trunk/php/src/gadgets/JsFeatureLoader.php
    incubator/shindig/trunk/php/src/gadgets/JsLibrary.php
    incubator/shindig/trunk/php/src/gadgets/JsLibraryFeatureFactory.php
    incubator/shindig/trunk/php/src/gadgets/LinkSpec.php
    incubator/shindig/trunk/php/src/gadgets/LocaleMessageBundle.php
    incubator/shindig/trunk/php/src/gadgets/LocaleSpec.php
    incubator/shindig/trunk/php/src/gadgets/MessageBundle.php
    incubator/shindig/trunk/php/src/gadgets/MessageBundleParser.php
    incubator/shindig/trunk/php/src/gadgets/Preload.php
    incubator/shindig/trunk/php/src/gadgets/ProxyGadgetContext.php
    incubator/shindig/trunk/php/src/gadgets/UserPref.php
    incubator/shindig/trunk/php/src/gadgets/UserPrefs.php
    incubator/shindig/trunk/php/src/gadgets/ViewSpec.php
    incubator/shindig/trunk/php/src/gadgets/rewrite/ContentRewriteFeature.php
    incubator/shindig/trunk/php/src/gadgets/rewrite/ContentRewriter.php
Modified:
    incubator/shindig/trunk/php/config/container.php
    incubator/shindig/trunk/php/index.php
    incubator/shindig/trunk/php/src/common/HttpServlet.php
    incubator/shindig/trunk/php/src/gadgets/Gadget.php
    incubator/shindig/trunk/php/src/gadgets/GadgetContext.php
    incubator/shindig/trunk/php/src/gadgets/GadgetFeatureRegistry.php
    incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php
    incubator/shindig/trunk/php/src/gadgets/MetadataContext.php
    incubator/shindig/trunk/php/src/gadgets/MetadataHandler.php
    incubator/shindig/trunk/php/src/gadgets/ProxyHandler.php
    incubator/shindig/trunk/php/src/gadgets/servlet/GadgetRenderingServlet.php
    incubator/shindig/trunk/php/src/gadgets/servlet/JsServlet.php
    incubator/shindig/trunk/php/src/gadgets/servlet/MetadataServlet.php
    incubator/shindig/trunk/php/src/gadgets/servlet/ProxyServlet.php
    incubator/shindig/trunk/php/test/misc/testGadget.xml

Modified: incubator/shindig/trunk/php/config/container.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/config/container.php?rev=744702&r1=744701&r2=744702&view=diff
==============================================================================
--- incubator/shindig/trunk/php/config/container.php (original)
+++ incubator/shindig/trunk/php/config/container.php Sun Feb 15 17:35:05 2009
@@ -50,9 +50,17 @@
 
   // Allow plain text security tokens, this is only here to allow the sample files to work. Disable on a production site
   'allow_plaintext_token' => true,
+
+  // Is a valid security token required to render a gadget? The token is required for doing signed preloads, but disallowing this
+  // can also help prevent external parties using your rendering server (only for the paranoid :)
+  'render_token_required' => false,
+
   // Compress the inlined javascript, saves upto 50% of the document size
   'compress_javascript' => true,
 
+  // Default refresh interval for proxy/makeRequest's if none is specified in the query
+  'default_refresh_interval' => 1209587,
+
   // The URL Prefix under which shindig lives ie if you have http://myhost.com/shindig/php set web_prefix to /shindig/php
   'web_prefix' => '',
   // If you changed the web prefix, add the prefix to these too

Modified: incubator/shindig/trunk/php/index.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/index.php?rev=744702&r1=744701&r2=744702&view=diff
==============================================================================
--- incubator/shindig/trunk/php/index.php (original)
+++ incubator/shindig/trunk/php/index.php Sun Feb 15 17:35:05 2009
@@ -50,15 +50,15 @@
     'src/common',
     'src/common/sample',
     'src/gadgets',
-    'src/gadgets/servlet', 
+    'src/gadgets/servlet',
     'src/gadgets/oauth',
     'src/gadgets/sample',
     'src/social',
-    'src/social/servlet', 
+    'src/social/servlet',
     'src/social/service',
     'src/social/opensocial',
     'src/social/model',
-    'src/social/spi', 
+    'src/social/spi',
     'src/social/converters',
     'src/social/oauth',
     'src/social/sample'
@@ -78,15 +78,15 @@
 }
 
 $servletMap = array(
-    Config::get('web_prefix') . '/gadgets/files' => 'FilesServlet', 
-    Config::get('web_prefix') . '/gadgets/js' => 'JsServlet', 
-    Config::get('web_prefix') . '/gadgets/proxy' => 'ProxyServlet', 
-    Config::get('web_prefix') . '/gadgets/makeRequest' => 'ProxyServlet', 
-    Config::get('web_prefix') . '/gadgets/ifr' => 'GadgetRenderingServlet', 
-    Config::get('web_prefix') . '/gadgets/metadata' => 'MetadataServlet', 
-    Config::get('web_prefix') . '/social/rest' => 'DataServiceServlet', 
-    Config::get('web_prefix') . '/social/rpc' => 'JsonRpcServlet', 
-    Config::get('web_prefix') . '/public.crt' => 'CertServlet', 
+    Config::get('web_prefix') . '/gadgets/files' => 'FilesServlet',
+    Config::get('web_prefix') . '/gadgets/js' => 'JsServlet',
+    Config::get('web_prefix') . '/gadgets/proxy' => 'ProxyServlet',
+    Config::get('web_prefix') . '/gadgets/makeRequest' => 'MakeRequestServlet',
+    Config::get('web_prefix') . '/gadgets/ifr' => 'GadgetRenderingServlet',
+    Config::get('web_prefix') . '/gadgets/metadata' => 'MetadataServlet',
+    Config::get('web_prefix') . '/social/rest' => 'DataServiceServlet',
+    Config::get('web_prefix') . '/social/rpc' => 'JsonRpcServlet',
+    Config::get('web_prefix') . '/public.crt' => 'CertServlet',
     Config::get('web_prefix') . '/public.cer' => 'CertServlet'
 );
 
@@ -109,7 +109,7 @@
 if ($servlet) {
   $class = new $class();
   $method = $_SERVER['REQUEST_METHOD'];
-  // Not all clients support the PUT, HEAD & DELETE http methods, they depend on the X-HTTP-Method-Override instead 
+  // Not all clients support the PUT, HEAD & DELETE http methods, they depend on the X-HTTP-Method-Override instead
   if ($method == 'POST' && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
     $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
   }

Modified: incubator/shindig/trunk/php/src/common/HttpServlet.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/common/HttpServlet.php?rev=744702&r1=744701&r2=744702&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/common/HttpServlet.php (original)
+++ incubator/shindig/trunk/php/src/common/HttpServlet.php Sun Feb 15 17:35:05 2009
@@ -53,7 +53,6 @@
     if (! $this->noHeaders) {
       header("Content-Type: $this->contentType" . (! empty($this->charset) ? "; charset={$this->charset}" : ''));
       header('Accept-Ranges: bytes');
-      $content = ob_get_contents();
       if ($this->noCache) {
         header("Cache-Control: no-cache, must-revalidate", true);
         header("Expires: Mon, 26 Jul 1997 05:00:00 GMT", true);
@@ -64,21 +63,6 @@
         header("Expires: " . gmdate("D, d M Y H:i:s", time() + $this->cacheTime) . " GMT", true);
         // Obey browsers (or proxy's) request to send a fresh copy if we recieve a no-cache pragma or cache-control request
         if (! isset($_SERVER['HTTP_PRAGMA']) || ! strstr(strtolower($_SERVER['HTTP_PRAGMA']), 'no-cache') && (! isset($_SERVER['HTTP_CACHE_CONTROL']) || ! strstr(strtolower($_SERVER['HTTP_CACHE_CONTROL']), 'no-cache'))) {
-          // If the browser send us a E-TAG check if it matches (md5 sum of content), if so send a not modified header instead of content
-          $etag = '"' . md5($content) . '"';
-          if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
-            header("ETag: \"$etag\"");
-            if ($this->lastModified) {
-              header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $this->lastModified) . ' GMT', true);
-            }
-            header("HTTP/1.1 304 Not Modified", true);
-            header('Content-Length: 0', true);
-            ob_end_clean();
-            die();
-          }
-          header("ETag: $etag");
-          // If no etag is present, then check if maybe this browser supports if_modified_since tags,
-          // check it against our lastModified (if it's set)
           if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $this->lastModified && ! isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
             $if_modified_since = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
             if ($this->lastModified <= $if_modified_since) {
@@ -104,8 +88,8 @@
   }
 
   /**
-   * Sets the time in seconds that the browser's cache should be 
-   * considered out of date (through the Expires header) 
+   * Sets the time in seconds that the browser's cache should be
+   * considered out of date (through the Expires header)
    *
    * @param int $time time in seconds
    */
@@ -123,7 +107,7 @@
   }
 
   /**
-   * Sets the content type of this request (forinstance: text/html or text/javascript, etc) 
+   * Sets the content type of this request (forinstance: text/html or text/javascript, etc)
    *
    * @param string $type content type header to use
    */
@@ -132,7 +116,7 @@
   }
 
   /**
-   * Returns the current content type 
+   * Returns the current content type
    *
    * @return string content type string
    */

Modified: incubator/shindig/trunk/php/src/gadgets/Gadget.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/Gadget.php?rev=744702&r1=744701&r2=744702&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/Gadget.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/Gadget.php Sun Feb 15 17:35:05 2009
@@ -18,275 +18,489 @@
  * under the License.
  */
 
-/**
- * The main gadget class, this gets filled in by the GadgetSpecParser, etc
- * and contains all the gadget information.
- *
- */
+class GadgetException extends Exception {}
+
 class Gadget {
-  private $jsLibraries;
-  private $substitutions;
-  private $userPrefValues;
-  private $oAuthSpec;
-  private $messageBundle = array();
-  private $checksum;
-  public $contentTypes = array('HTML', 'URL');
-  public $id;
-  public $author;
-  public $authorEmail;
-  public $description;
-  public $directoryTitle;
-  public $contentData = array();
-  public $localeSpecs = array();
-  public $preloads = array();
-  public $requires = array();
-  public $screenshot;
-  public $thumbnail;
-  public $title;
-  public $titleUrl = null;
-  public $userPrefs = array();
-  public $authorAffiliation;
-  public $authorLocation;
-  public $authorPhoto;
-  public $authorAboutMe;
-  public $authorQuote;
-  public $authorLink;
-  public $showStats;
-  public $showInDirectory;
-  public $string;
-  public $width;
-  public $height;
-  public $category;
-  public $category2;
-  public $singleton;
-  public $renderInline;
-  public $scaling;
-  public $scrolling;
-  public $views = array();
-  public $links = array();
-  public $icons = array();
-
-  public function __construct($id = false, $context) {
-    if ($id) {
-      $this->id = $id;
-    }
-    if ($context->getUserPrefs()) {
-      $this->setPrefs($context->getUserPrefs());
+  const DEFAULT_VIEW = 'profile';
+  public $gadgetSpec;
+  public $features;
+  public $substitutions;
+  public $rightToLeft;
+  public $gadgetContext;
+
+  public function __construct(GadgetSpec $gadgetSpec, GadgetContext $gadgetContext) {
+    $this->gadgetSpec = $gadgetSpec;
+    $this->gadgetContext = $gadgetContext;
+  }
+
+  public function getView($viewName) {
+    if (isset($this->gadgetSpec->views[$viewName])) {
+      return $this->gadgetSpec->views[$viewName];
+    } elseif (isset($this->gadgetSpec->views[self::DEFAULT_VIEW])) {
+      return $this->gadgetSpec->views[self::DEFAULT_VIEW];
     }
-    $this->substitutions = new Substitutions();
-    $this->jsLibraries = array();
+    throw new GadgetException("Invalid view specified for this gadget");
   }
 
-  public function setId($id) {
-    $this->id = $id;
+  /**
+   * @return unknown
+   */
+  public function getAuthor() {
+    return $this->substitutions->substitute($this->gadgetSpec->author);
   }
 
-  public function setPrefs($prefs) {
-    $this->userPrefValues = $prefs;
+  /**
+   * @return unknown
+   */
+  public function getAuthorAboutme() {
+    return $this->substitutions->substitute($this->gadgetSpec->authorAboutme);
   }
 
-  public function getAuthor() {
-    return $this->substitutions->substitute($this->author);
+  /**
+   * @return unknown
+   */
+  public function getAuthorAffiliation() {
+    return $this->substitutions->substitute($this->gadgetSpec->authorAffiliation);
   }
 
+  /**
+   * @return unknown
+   */
   public function getAuthorEmail() {
-    return $this->substitutions->substitute($this->authorEmail);
+    return $this->substitutions->substitute($this->gadgetSpec->authorEmail);
+  }
+
+  /**
+   * @return unknown
+   */
+  public function getAuthorLink() {
+    return $this->substitutions->substitute($this->gadgetSpec->authorLink);
+  }
+
+  /**
+   * @return unknown
+   */
+  public function getAuthorLocation() {
+    return $this->substitutions->substitute($this->gadgetSpec->authorLocation);
+  }
+
+  /**
+   * @return unknown
+   */
+  public function getAuthorPhoto() {
+    return $this->substitutions->substitute($this->gadgetSpec->authorPhoto);
+  }
+
+  /**
+   * @return unknown
+   */
+  public function getAuthorQuote() {
+    return $this->substitutions->substitute($this->gadgetSpec->authorQuote);
   }
 
-  public function getMessageBundle() {
-    return $this->messageBundle;
+  /**
+   * @return unknown
+   */
+  public function getCategory() {
+    return $this->substitutions->substitute($this->gadgetSpec->category);
+  }
+
+  /**
+   * @return unknown
+   */
+  public function getCategory2() {
+    return $this->substitutions->substitute($this->gadgetSpec->category2);
+  }
+
+  /**
+   * @return unknown
+   */
+  public function getChecksum() {
+    return $this->gadgetSpec->checksum;
   }
 
+  /**
+   * @return unknown
+   */
   public function getDescription() {
-    return $this->substitutions->substitute($this->description);
+    return $this->substitutions->substitute($this->gadgetSpec->description);
   }
 
+  /**
+   * @return unknown
+   */
   public function getDirectoryTitle() {
-    return $this->substitutions->substitute($this->directoryTitle);
+    return $this->substitutions->substitute($this->gadgetSpec->directoryTitle);
   }
 
-  public function getId() {
-    return $this->id;
+  /**
+   * @return unknown
+   */
+  public function getHeight() {
+    return $this->substitutions->substitute($this->gadgetSpec->height);
   }
 
-  public function getJsLibraries() {
-    return $this->jsLibraries;
+  /**
+   * @return unknown
+   */
+  public function getIcon() {
+    return $this->substitutions->substitute($this->gadgetSpec->icon);
   }
 
-  public function addJsLibrary($library) {
-    $this->jsLibraries[] = $library;
+  /**
+   * @return unknown
+   */
+  public function getLinks() {
+    return $this->gadgetSpec->links;
   }
 
-  public function getLocaleSpecs() {
-    return $this->localeSpecs;
+  /**
+   * @return unknown
+   */
+  public function getLocales() {
+    return $this->gadgetSpec->locales;
   }
 
-  public function getFeatureParams($gadget, $feature) {
-    //FIXME not working atm
-    $spec = $gadget->getRequires();
-    $spec = isset($spec[$feature->getName()]) ? $spec[$feature->getName()] : null;
-    if ($spec == null) {
-      return array();
-    } else {
-      return $spec->getParams();
-    }
+  /**
+   * @return unknown
+   */
+  public function getOptionalFeatures() {
+    return $this->gadgetSpec->optionalFeatures;
   }
 
+  /**
+   * @return unknown
+   */
   public function getPreloads() {
-    return $this->preloads;
+    return $this->gadgetSpec->preloads;
   }
 
-  public function getRequires() {
-    return $this->requires;
+  /**
+   * @return unknown
+   */
+  public function getRenderInline() {
+    return $this->substitutions->substitute($this->gadgetSpec->renderInline);
   }
 
+  /**
+   * @return unknown
+   */
+  public function getRequiredFeatures() {
+    return $this->substitutions->substitute($this->gadgetSpec->requiredFeatures);
+  }
+
+  /**
+   * @return unknown
+   */
+  public function getScaling() {
+    return $this->substitutions->substitute($this->gadgetSpec->scaling);
+  }
+
+  /**
+   * @return unknown
+   */
   public function getScreenshot() {
-    return $this->substitutions->substitute($this->screenshot);
+    return $this->substitutions->substitute($this->gadgetSpec->screenshot);
   }
 
-  public function getSubstitutions() {
-    return $this->substitutions;
+  /**
+   * @return unknown
+   */
+  public function getScrolling() {
+    return $this->substitutions->substitute($this->gadgetSpec->scrolling);
+  }
+
+  /**
+   * @return unknown
+   */
+  public function getShowInDirectory() {
+    return $this->substitutions->substitute($this->gadgetSpec->showInDirectory);
+  }
+
+  /**
+   * @return unknown
+   */
+  public function getShowStats() {
+    return $this->substitutions->substitute($this->gadgetSpec->showStats);
   }
 
+  /**
+   * @return unknown
+   */
+  public function getSingleton() {
+    return $this->substitutions->substitute($this->gadgetSpec->singleton);
+  }
+
+  /**
+   * @return unknown
+   */
+  public function getString() {
+    return $this->substitutions->substitute($this->gadgetSpec->string);
+  }
+
+  /**
+   * @return unknown
+   */
   public function getThumbnail() {
-    return $this->substitutions->substitute($this->thumbnail);
+    return $this->substitutions->substitute($this->gadgetSpec->thumbnail);
   }
 
+  /**
+   * @return unknown
+   */
   public function getTitle() {
-    return $this->substitutions->substitute($this->title);
+    return $this->substitutions->substitute($this->gadgetSpec->title);
   }
 
+  /**
+   * @return unknown
+   */
   public function getTitleUrl() {
-    $ret = null;
-    if (! empty($this->titleUrl)) {
-      $ret = $this->substitutions->substitute($this->titleUrl);
-    }
-    return $ret;
+    return $this->substitutions->substitute($this->gadgetSpec->titleUrl);
   }
 
-  public function getAuthorAffiliation() {
-    return $this->substitutions->substitute($this->authorAffiliation);
+  /**
+   * @return unknown
+   */
+  public function getUserPrefs() {
+    return $this->gadgetSpec->userPrefs;
   }
 
-  public function getAuthorLocation() {
-    return $this->substitutions->substitute($this->authorLocation);
+  /**
+   * @return unknown
+   */
+  public function getWidth() {
+    return $this->substitutions->substitute($this->gadgetSpec->width);
   }
 
-  public function getAuthorPhoto() {
-    return $this->substitutions->substitute($this->authorPhoto);
+  /**
+   * @param unknown_type $author
+   */
+  public function setAuthor($author) {
+    $this->gadgetSpec->author = $author;
   }
 
-  public function getAuthorAboutme() {
-    return $this->substitutions->substitute($this->authorAboutMe);
+  /**
+   * @param unknown_type $authorAboutme
+   */
+  public function setAuthorAboutme($authorAboutme) {
+    $this->gadgetSpec->authorAboutme = $authorAboutme;
   }
 
-  public function getAuthorQuote() {
-    return $this->substitutions->substitute($this->authorQuote);
+  /**
+   * @param unknown_type $authorAffiliation
+   */
+  public function setAuthorAffiliation($authorAffiliation) {
+    $this->gadgetSpec->authorAffiliation = $authorAffiliation;
   }
 
-  public function getAuthorLink() {
-    return $this->substitutions->substitute($this->authorLink);
+  /**
+   * @param unknown_type $authorEmail
+   */
+  public function setAuthorEmail($authorEmail) {
+    $this->gadgetSpec->authorEmail = $authorEmail;
   }
 
-  public function getShowStats() {
-    return $this->showStats;
+  /**
+   * @param unknown_type $authorLink
+   */
+  public function setAuthorLink($authorLink) {
+    $this->gadgetSpec->authorLink = $authorLink;
   }
 
-  public function getShowInDirectory() {
-    return $this->showInDirectory;
+  /**
+   * @param unknown_type $authorLocation
+   */
+  public function setAuthorLocation($authorLocation) {
+    $this->gadgetSpec->authorLocation = $authorLocation;
   }
 
-  public function getString() {
-    return $this->substitutions->substitute($this->string);
+  /**
+   * @param unknown_type $authorPhoto
+   */
+  public function setAuthorPhoto($authorPhoto) {
+    $this->gadgetSpec->authorPhoto = $authorPhoto;
   }
 
-  public function getWidth() {
-    return $this->width;
+  /**
+   * @param unknown_type $authorQuote
+   */
+  public function setAuthorQuote($authorQuote) {
+    $this->gadgetSpec->authorQuote = $authorQuote;
   }
 
-  public function getHeight() {
-    return $this->height;
+  /**
+   * @param unknown_type $category
+   */
+  public function setCategory($category) {
+    $this->gadgetSpec->category = $category;
   }
 
-  public function getCategory() {
-    return $this->substitutions->substitute($this->category);
+  /**
+   * @param unknown_type $category2
+   */
+  public function setCategory2($category2) {
+    $this->gadgetSpec->category2 = $category2;
   }
 
-  public function getCategory2() {
-    return $this->substitutions->substitute($this->category2);
+  /**
+   * @param unknown_type $checksum
+   */
+  public function setChecksum($checksum) {
+    $this->gadgetSpec->checksum = $checksum;
   }
 
-  public function getSingleton() {
-    return $this->singleton;
+  /**
+   * @param unknown_type $description
+   */
+  public function setDescription($description) {
+    $this->gadgetSpec->description = $description;
   }
 
-  public function getRenderInline() {
-    return $this->renderInline;
+  /**
+   * @param unknown_type $directoryTitle
+   */
+  public function setDirectoryTitle($directoryTitle) {
+    $this->gadgetSpec->directoryTitle = $directoryTitle;
   }
 
-  public function getScaling() {
-    return $this->scaling;
+  /**
+   * @param unknown_type $height
+   */
+  public function setHeight($height) {
+    $this->gadgetSpec->height = $height;
   }
 
-  public function getScrolling() {
-    return $this->scrolling;
+  /**
+   * @param unknown_type $icon
+   */
+  public function setIcon($icon) {
+    $this->gadgetSpec->icon = $icon;
   }
 
-  public function getUserPrefs() {
-    return $this->userPrefs;
+  /**
+   * @param unknown_type $links
+   */
+  public function setLinks($links) {
+    $this->gadgetSpec->links = $links;
   }
 
-  public function getUserPrefValues() {
-    return $this->userPrefValues;
+  /**
+   * @param unknown_type $locales
+   */
+  public function setLocales($locales) {
+    $this->gadgetSpec->locales = $locales;
   }
 
-  public function setMessageBundle($messageBundle) {
-    $this->messageBundle = $messageBundle;
+  /**
+   * @param unknown_type $optionalFeatures
+   */
+  public function setOptionalFeatures($optionalFeatures) {
+    $this->gadgetSpec->optionalFeatures = $optionalFeatures;
   }
 
-  public function getLinks() {
-    return $this->links;
+  /**
+   * @param unknown_type $preloads
+   */
+  public function setPreloads($preloads) {
+    $this->gadgetSpec->preloads = $preloads;
   }
 
-  public function getLink($rel) {
-    foreach ($this->links as $link) {
-      if ($link->getRel() == $rel) {
-        return $link;
-      }
-    }
-    return false;
+  /**
+   * @param unknown_type $renderInline
+   */
+  public function setRenderInline($renderInline) {
+    $this->gadgetSpec->renderInline = $renderInline;
   }
 
-  public function getIcons() {
-    return $this->icons;
+  /**
+   * @param unknown_type $requiredFeatures
+   */
+  public function setRequiredFeatures($requiredFeatures) {
+    $this->gadgetSpec->requiredFeatures = $requiredFeatures;
   }
 
-  public function getViews() {
-    return $this->views;
+  /**
+   * @param unknown_type $scaling
+   */
+  public function setScaling($scaling) {
+    $this->gadgetSpec->scaling = $scaling;
   }
 
-  public function getView($viewName) {
-    if (isset($this->views[$viewName])) {
-      return $this->views[$viewName];
-    } elseif (isset($this->views[DEFAULT_VIEW])) {
-      return $this->views[DEFAULT_VIEW];
-    }
-    throw new GadgetException("Invalid view specified for this gadget");
+  /**
+   * @param unknown_type $screenshot
+   */
+  public function setScreenshot($screenshot) {
+    $this->gadgetSpec->screenshot = $screenshot;
   }
 
-  public function getOAuthSpec() {
-    return $this->oAuthSpec;
+  /**
+   * @param unknown_type $scrolling
+   */
+  public function setScrolling($scrolling) {
+    $this->gadgetSpec->scrolling = $scrolling;
   }
 
-  public function setOAuthSpec($oAuthSpec) {
-    $this->oAuthSpec = $oAuthSpec;
+  /**
+   * @param unknown_type $showInDirectory
+   */
+  public function setShowInDirectory($showInDirectory) {
+    $this->gadgetSpec->showInDirectory = $showInDirectory;
   }
 
-  public function setChecksum($xml) {
-    $this->checksum = md5($xml);
+  /**
+   * @param unknown_type $showStats
+   */
+  public function setShowStats($showStats) {
+    $this->gadgetSpec->showStats = $showStats;
   }
 
-  public function getChecksum() {
-    return $this->checksum;
+  /**
+   * @param unknown_type $singleton
+   */
+  public function setSingleton($singleton) {
+    $this->gadgetSpec->singleton = $singleton;
+  }
+
+  /**
+   * @param unknown_type $string
+   */
+  public function setString($string) {
+    $this->gadgetSpec->string = $string;
+  }
+
+  /**
+   * @param unknown_type $thumbnail
+   */
+  public function setThumbnail($thumbnail) {
+    $this->gadgetSpec->thumbnail = $thumbnail;
+  }
+
+  /**
+   * @param unknown_type $title
+   */
+  public function setTitle($title) {
+    $this->gadgetSpec->title = $title;
+  }
+
+  /**
+   * @param unknown_type $titleUrl
+   */
+  public function setTitleUrl($titleUrl) {
+    $this->gadgetSpec->titleUrl = $titleUrl;
+  }
+
+  /**
+   * @param unknown_type $userPrefs
+   */
+  public function setUserPrefs($userPrefs) {
+    $this->gadgetSpec->userPrefs = $userPrefs;
+  }
+
+  /**
+   * @param unknown_type $width
+   */
+  public function setWidth($width) {
+    $this->gadgetSpec->width = $width;
   }
 }

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetContext.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetContext.php?rev=744702&r1=744701&r2=744702&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetContext.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetContext.php Sun Feb 15 17:35:05 2009
@@ -18,7 +18,7 @@
  * under the License.
  */
 
-define('DEFAULT_VIEW', 'profile');
+
 
 /*
  * GadgetContext contains all contextual variables and classes that are relevant for this request,
@@ -26,11 +26,11 @@
  * Server wide variables are stored in config.php
  */
 class GadgetContext {
+  const DEFAULT_VIEW = 'profile';
   protected $httpFetcher = null;
   protected $locale = null;
   protected $renderingContext = null;
   protected $registry = null;
-  protected $userPrefs = null;
   protected $gadgetId = null;
   protected $view = null;
   protected $moduleId = null;
@@ -59,7 +59,7 @@
   }
 
   private function getRefreshIntervalParam() {
-    return isset($_GET['refresh']) ? $_GET['refresh'] : Config::get('cache_time');
+    return isset($_GET['refresh']) ? $_GET['refresh'] : Config::get('default_refresh_interval');
   }
 
   private function getContainerParam() {
@@ -100,7 +100,7 @@
   }
 
   private function getViewParam() {
-    return ! empty($_GET['view']) ? $_GET['view'] : DEFAULT_VIEW;
+    return ! empty($_GET['view']) ? $_GET['view'] : self::DEFAULT_VIEW;
   }
 
   private function instanceBlacklist() {
@@ -112,18 +112,6 @@
     }
   }
 
-  private function instanceUserPrefs() {
-    $prefs = array();
-    $userPrefParamPrefix = Config::get('userpref_param_prefix');
-    foreach ($_GET as $key => $val) {
-      if (substr($key, 0, strlen($userPrefParamPrefix)) == $userPrefParamPrefix) {
-        $name = substr($key, strlen($userPrefParamPrefix));
-        $prefs[$name] = $val;
-      }
-    }
-    return new UserPrefs($prefs);
-  }
-
   private function instanceGadgetId($url, $moduleId) {
     return new GadgetId($url, $moduleId);
   }
@@ -134,8 +122,7 @@
   }
 
   private function instanceRegistry() {
-    // Profiling showed 40% of the processing time was spend in the feature registry
-    // So by caching this and making it a one time initialization, we almost double the performance
+    // feature parsing is very resource intensive so by caching the result this saves upto 30% of the processing time
     $featureCache = Config::get('feature_cache');
     $featureCache = new $featureCache();
     if (! ($registry = $featureCache->get(md5(Config::get('features_path'))))) {
@@ -149,7 +136,7 @@
     // Get language and country params, try the GET params first, if their not set try the POST, else use 'all' as default
     $language = ! empty($_GET['lang']) ? $_GET['lang'] : (! empty($_POST['lang']) ? $_POST['lang'] : 'all');
     $country = ! empty($_GET['country']) ? $_GET['country'] : (! empty($_POST['country']) ? $_POST['country'] : 'all');
-    return new Locale($language, $country);
+    return array('lang' => strtolower($language), 'country' => strtoupper($country));
   }
 
   private function instanceContainerConfig() {
@@ -189,13 +176,6 @@
     return $this->url;
   }
 
-  public function getUserPrefs() {
-    if ($this->userPrefs == null) {
-      $this->setUserPrefs($this->instanceUserPrefs());
-    }
-    return $this->userPrefs;
-  }
-
   public function getView() {
     return $this->view;
   }
@@ -248,10 +228,6 @@
     $this->url = $url;
   }
 
-  public function setUserPrefs($userPrefs) {
-    $this->userPrefs = $userPrefs;
-  }
-
   public function setView($view) {
     $this->view = $view;
   }
@@ -301,10 +277,6 @@
     return $this->locale;
   }
 
-  public function getFeatureRegistry() {
-    return $this->registry;
-  }
-
   /**
    * Extracts the 'st' token from the GET or POST params and calls the
    * signer to validate the token
@@ -323,6 +295,9 @@
     if (count(explode(':', $token)) != 6) {
       $token = urldecode(base64_decode($token));
     }
+    if (empty($token)) {
+      throw new Exception("Missing or invalid security token");
+    }
     return $signer->createToken($token);
   }
 }

Added: incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php?rev=744702&view=auto
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php (added)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php Sun Feb 15 17:35:05 2009
@@ -0,0 +1,308 @@
+<?php
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * The Gadget Factory builds a gadget based on the current context and token and returns a fully processed
+ * gadget ready to be rendered.
+ *
+ */
+class GadgetFactory {
+  private $context;
+  private $token;
+
+  public function __construct(GadgetContext $context, $token) {
+    $this->context = $context;
+    $this->token = $token;
+  }
+
+  /**
+   * Returns the processed gadget spec
+   *
+   * @return GadgetSpec
+   */
+  public function createGadget() {
+    $gadgetUrl = $this->context->getUrl();
+    if ($this->context->getBlacklist() != null && $this->context->getBlacklist()->isBlacklisted($gadgetUrl)) {
+      throw new GadgetException("The Gadget ($gadgetUrl) is blacklisted and can not be rendered");
+    }
+    // Fetch the gadget's content and create a GadgetSpec
+    $gadgetContent = $this->fetchGadget($gadgetUrl);
+    $gadgetSpecParser = new GadgetSpecParser();
+    $gadgetSpec = $gadgetSpecParser->parse($gadgetContent);
+    $gadget = new Gadget($gadgetSpec, $this->context);
+
+    // Process the gadget: fetching remote resources, processing & applying the correct translations, user prefs and feature resolving
+    $this->fetchResources($gadget);
+    $this->mergeLocales($gadget);
+    $this->parseUserPrefs($gadget);
+    $this->addSubstitutions($gadget);
+    $this->applySubstitutions($gadget);
+    $this->parseFeatures($gadget);
+    return $gadget;
+  }
+
+  /**
+   * Resolves the Required and Optional features and their dependencies into a real feature list using
+   * the GadgetFeatureRegistry, which can be used to construct the javascript for the gadget
+   *
+   * @param Gadget $gadget
+   */
+  private function parseFeatures(Gadget &$gadget) {
+    $found = $missing = array();
+    if (!$this->context->getRegistry()->resolveFeatures(array_merge($gadget->gadgetSpec->requiredFeatures, $gadget->gadgetSpec->optionalFeatures), $found, $missing)) {
+      $requiredMissing = false;
+      foreach ($missing as $featureName) {
+        if (in_array($featureName, $gadget->gadgetSpec->requiredFeatures)) {
+          $requiredMissing = true;
+          break;
+        }
+      }
+      if ($requiredMissing) {
+        throw new GadgetException("Unknown features: ".implode(',', $missing));
+      }
+    }
+    unset($gadget->gadgetSpec->optionalFeatures);
+    unset($gadget->gadgetSpec->requiredFeatures);
+    $gadget->features = $found;
+  }
+
+  /**
+   * Applies the substitutions to the complex types (preloads, user prefs, etc). Simple
+   * types (author, title, etc) are translated on the fly in the gadget's getFoo() functions
+   */
+  private function applySubstitutions(Gadget &$gadget) {
+    // Apply the substitutions to the UserPrefs
+    foreach ($gadget->gadgetSpec->userPrefs as $key => $pref) {
+      $gadget->gadgetSpec->userPrefs[$key]['name'] = $gadget->substitutions->substitute($pref['name']);
+      $gadget->gadgetSpec->userPrefs[$key]['displayName'] = $gadget->substitutions->substitute($pref['displayName']);
+      $gadget->gadgetSpec->userPrefs[$key]['required'] = $gadget->substitutions->substitute($pref['required']);
+      $gadget->gadgetSpec->userPrefs[$key]['datatype'] = $gadget->substitutions->substitute($pref['datatype']);
+      $gadget->gadgetSpec->userPrefs[$key]['defaultValue'] = $gadget->substitutions->substitute($pref['defaultValue']);
+      $gadget->gadgetSpec->userPrefs[$key]['value'] = $gadget->substitutions->substitute($pref['value']);
+      if (isset($pref['enumValues'])) {
+        foreach ($pref['enumValues'] as $enumKey => $enumVal) {
+          $gadget->gadgetSpec->userPrefs[$key]['enumValues'][$enumKey]['value'] = $gadget->substitutions->substitute($enumVal['value']);
+          $gadget->gadgetSpec->userPrefs[$key]['enumValues'][$enumKey]['displayValue'] = $gadget->substitutions->substitute($enumVal['displayValue']);
+        }
+      }
+    }
+    // Apply substitutions to the preloads
+    foreach ($gadget->gadgetSpec->preloads as $url => $preload) {
+      $gadget->gadgetSpec->preloads[$url]['body'] = $gadget->substitutions->substitute($preload['body']);
+    }
+  }
+
+  /**
+   * Seeds the substitutions class with the user prefs, messages, bidi and module id
+   */
+  private function addSubstitutions(Gadget &$gadget) {
+    $gadget->substitutions = new Substitutions();
+    if ($this->token) {
+      $gadget->substitutions->addSubstitution('MODULE', "ID", $this->token->getModuleId());
+    }
+    if ($gadget->gadgetSpec->locales) {
+      $gadget->substitutions->addSubstitutions('MSG', $gadget->gadgetSpec->locales);
+    }
+    $gadget->substitutions->addSubstitution('BIDI', "START_EDGE", $gadget->rightToLeft ? "right" : "left");
+    $gadget->substitutions->addSubstitution('BIDI', "END_EDGE", $gadget->rightToLeft ? "left" : "right");
+    $gadget->substitutions->addSubstitution('BIDI', "DIR", $gadget->rightToLeft ? "rtl" : "ltr");
+    $gadget->substitutions->addSubstitution('BIDI', "REVERSE_DIR", $gadget->rightToLeft ? "ltr" : "rtl");
+    foreach ($gadget->gadgetSpec->userPrefs as $pref) {
+      $gadget->substitutions->addSubstitution('UP', $gadget->substitutions->substitute($pref['name']), $gadget->substitutions->substitute($pref['value']));
+    }
+  }
+  /**
+   * Process the UserPrefs values based on the current context
+   *
+   * @param Gadget $gadget
+   */
+  private function parseUserPrefs(Gadget &$gadget) {
+    foreach ($gadget->gadgetSpec->userPrefs as $key => $pref) {
+      $queryKey = 'up_'.$pref['name'];
+      $gadget->gadgetSpec->userPrefs[$key]['value'] = isset($_GET[$queryKey]) ? trim(urldecode($_GET[$queryKey])) : $pref['defaultValue'];
+    }
+  }
+
+  /**
+   * Merges all matching Message bundles, with a full match (lang and country) having the
+   * highest priority and all/all having the lowest.
+   *
+   * This distills the locales array's back to one array of translations, which is then exposed
+   * through the $gadget->substitutions class
+   *
+   * @param Gadget $gadget
+   */
+  private function mergeLocales(Gadget $gadget) {
+    if (count($gadget->gadgetSpec->locales)) {
+      $contextLocale = $this->context->getLocale();
+      $locales = $gadget->gadgetSpec->locales;
+      $gadget->rightToLeft  = false;
+      $full = $partial = $all = null;
+      foreach ($locales as $locale) {
+        if ($locale['lang'] == $contextLocale['lang'] && $locale['country'] == $contextLocale['country']) {
+          $full = $locale['messageBundle'];
+          $gadget->rightToLeft = $locale['languageDirection'] == 'rtl';
+        } elseif ($locale['lang'] == $contextLocale['lang'] && $locale['country'] == 'all') {
+          $partial = $locale['messageBundle'];
+        } elseif ($locale['country'] == 'all' && $locale['lang'] == 'all') {
+          $all = $locale['messageBundle'];
+        }
+      }
+      $gadget->gadgetSpec->locales = array();
+      // array_merge overwrites duplicate keys from param 2 over param 1, so $full takes precedence over partial, and it over all
+      if ($full) $gadget->gadgetSpec->locales = array_merge($full, $gadget->gadgetSpec->locales);
+      if ($partial) $gadget->gadgetSpec->locales = array_merge($partial, $gadget->gadgetSpec->locales);
+      if ($all) $gadget->gadgetSpec->locales = array_merge($all, $gadget->gadgetSpec->locales);
+    }
+  }
+
+  /**
+   * Fetches all remote resources simultaniously using a multiFetchRequest to optimize rendering time.
+   *
+   * The preloads will be json_encoded to their gadget document injection format, and the locales will
+   * be reduced to only the GadgetContext->getLocale matching entries.
+   *
+   * @param Gadget $gadget
+   * @param GadgetContext $context
+   */
+  private function fetchResources(Gadget &$gadget) {
+    $contextLocale = $this->context->getLocale();
+    $unsignedRequests = $unsignedContexts = $signedRequests = array();
+    foreach ($gadget->getLocales() as $key => $locale) {
+      // Only fetch the locales that match the current context's language and country
+      if (($locale['country'] == 'all' && $locale['lang'] == 'all') || ($locale['lang'] == $contextLocale['lang'] && $locale['country'] == 'all') || ($locale['lang'] == $contextLocale['lang'] && $locale['country'] == $contextLocale['country'])) {
+        if (!empty($locale['messages'])) {
+          // locale matches the current context, add it to the requests queue
+          $unsignedRequests[] = $locale['messages'];
+        }
+      } else {
+        // remove any locales that are not applicable to this context
+        unset($gadget->gadgetSpec->locales[$key]);
+      }
+    }
+    // Add preloads to the request queue
+    foreach ($gadget->getPreloads() as $preload) {
+      if (!empty($preload['href'])) {
+        if (!empty($preload['authz']) && $preload['authz'] == 'SIGNED') {
+          $signedRequests[] = $preload['href'];
+        } else {
+          if ($this->token == '') {
+            throw new GadgetException("Signed preloading requested, but no valid security token set");
+          }
+          $unsignedRequests[] = $preload['href'];
+        }
+      }
+    }
+    // Perform the non-signed requests
+    foreach ($unsignedRequests as $key => $requestUrl) {
+      $request = new RemoteContentRequest($requestUrl);
+      $request->createRemoteContentRequestWithUri($requestUrl);
+      $unsignedRequests[$key] = $request;
+      $unsignedContexts[$key] = $this->context;
+    }
+    $responses = array();
+    if (count($unsignedRequests)) {
+      $brc = new BasicRemoteContent();
+      $resps = $brc->multiFetch($unsignedRequests, $unsignedContexts);
+      foreach ($resps as $response) {
+        $responses[$response->getUrl()] = array(
+            'body' => $response->getResponseContent(),
+            'rc' => $response->getHttpCode());
+      }
+    }
+    // Perform the signed requests
+    foreach ($signedRequests as $key => $requestUrl) {
+      $request = new RemoteContentRequest($requestUrl);
+      $request->createRemoteContentRequestWithUri($requestUrl);
+      $signedRequests[$key] = $request;
+      $signingFetcherFactory = new SigningFetcherFactory(Config::get("private_key_file"));
+      $fetcher = $signingFetcherFactory->getSigningFetcher(new BasicRemoteContentFetcher(), $this->token);
+      $req = $fetcher->signRequest($requestUrl, 'GET');
+      $req->setNotSignedUri($requestUrl);
+      $signedRequests[] = $req;
+    }
+    if (count($signedRequests)) {
+      $fetcher = $signingFetcherFactory->getSigningFetcher(new BasicRemoteContentFetcher(), $this->token);
+      $resps = $fetcher->multiFetchRequest($signedRequests);
+      foreach ($resps as $response) {
+        $responses[$response->getNotSignedUrl()] = array(
+            'body' => $response->getResponseContent(),
+            'rc' => $response->getHttpCode());
+      }
+    }
+    // assign the results to the gadget locales and preloads (using the url as the key)
+    foreach ($gadget->gadgetSpec->locales as $key => $locale) {
+      if (!empty($locale['messages']) && isset($responses[$locale['messages']]) && $responses[$locale['messages']]['rc'] == 200) {
+        $gadget->gadgetSpec->locales[$key]['messageBundle'] = $this->parseMessageBundle($responses[$locale['messages']]['body']);
+      }
+    }
+    $preloads = array();
+    foreach ($gadget->gadgetSpec->preloads as $key => $preload) {
+      if (!empty($preload['href']) && isset($responses[$preload['href']]) && $responses[$preload['href']]['rc'] == 200) {
+        $preloads[$preload['href']] = $responses[$preload['href']];
+      }
+    }
+    $gadget->gadgetSpec->preloads = $preloads;
+  }
+
+  /**
+   * Parses the (remote / fetched) message bundle xml
+   *
+   * @param string $messageBundleData
+   * @return array (MessageBundle)
+   */
+  private function parseMessageBundle($messageBundleData) {
+    libxml_use_internal_errors(true);
+    $doc = new DOMDocument();
+    if (! $doc->loadXML($messageBundleData, LIBXML_NOCDATA)) {
+      $errors = libxml_get_errors();
+      $errorStr = '';
+      foreach ($errors as $error) {
+        $errorStr .= $error . " \n";
+      }
+      libxml_clear_errors();
+      throw new GadgetSpecException("Error parsing gadget xml:\n$errorStr");
+    }
+    $messageBundle = array();
+    if (($messageBundleNode = $doc->getElementsByTagName('messagebundle')) != null && $messageBundleNode->length > 0) {
+      $messageBundleNode = $messageBundleNode->item(0);
+      $messages = $messageBundleNode->getElementsByTagName('msg');
+      foreach ($messages as $msg) {
+        $messageBundle[$msg->getAttribute('name')] = trim($msg->nodeValue);
+      }
+    }
+    return $messageBundle;
+  }
+
+  /**
+   * Fetches the gadget xml for the requested URL using the http fetcher
+   *
+   * @param unknown_type $gadgetUrl
+   * @return string gadget's xml content
+   */
+  private function fetchGadget($gadgetUrl) {
+    $request = new RemoteContentRequest($gadgetUrl);
+    $xml = $this->context->getHttpFetcher()->fetch($request, $this->context);
+    if ($xml->getHttpCode() != '200') {
+      throw new GadgetException("Failed to retrieve gadget content (recieved http code " . $xml->getHttpCode() . ")");
+    }
+    return $xml->getResponseContent();
+  }
+}

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetFeatureRegistry.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetFeatureRegistry.php?rev=744702&r1=744701&r2=744702&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetFeatureRegistry.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetFeatureRegistry.php Sun Feb 15 17:35:05 2009
@@ -1,4 +1,5 @@
 <?php
+
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,64 +19,59 @@
  * under the License.
  */
 
+/**
+ * Class that deals with the processing, loading and dep resolving of the gadget features
+ * Features are javascript libraries that provide an API, like 'opensocial' or 'settitle'
+ *
+ */
 class GadgetFeatureRegistry {
-  private $features = array();
-  private $core = array();
+  public $features;
   private $coreDone = false;
+  private $coreFeaturs;
 
   public function __construct($featurePath) {
     $this->registerFeatures($featurePath);
   }
 
-  public function registerFeatures($featurePath) {
-    if (empty($featurePath) || $featurePath == null) {
-      return;
-    }
-    $loader = new JsFeatureLoader();
-    $jsFeatures = $loader->loadFeatures($featurePath, $this);
-    if (! $this->coreDone) {
-      foreach ($jsFeatures as $entry) {
-        if (strtolower(substr($entry->name, 0, strlen('core'))) == 'core') {
-          $this->core[$entry->name] = $entry->name;
-        }
-      }
-      // Make sure non-core features depend on core.
-      foreach ($jsFeatures as $entry) {
-        if (strtolower(substr($entry->name, 0, strlen('core'))) != 'core') {
-          $entry->deps = array_merge($entry->deps, $this->core);
-        }
-      }
-      $this->coreDone = true;
-    }
-  }
-
-  public function register($name, $deps, $feature) {
-    // Core entries must come first.
-    $entry = isset($this->features[$name]) ? $this->features[$name] : null;
-    if ($entry == null) {
-      $entry = new GadgetFeatureRegistryEntry($name, $deps, $feature, $this);
-      if ($this->coreDone) {
-        $entry->deps = array_merge($entry->deps, $this->core);
+  public function getFeatureContent($feature, GadgetContext $context, $isGadgetContext) {
+    if (!isset($this->features[$feature])) {
+      throw new GadgetException("Invalid feature: ".htmlentities($feature));
+    }
+    $feature = $this->features[$feature];
+    $filesContext = $isGadgetContext ? 'gadgetJs' : 'containerJs';
+    if (!isset($feature[$filesContext])) {
+      // no javascript specified for this context
+      return '';
+    }
+    $ret = '';
+    foreach ($feature[$filesContext] as $entry) {
+      switch ($entry['type']) {
+        case 'URL':
+          $request = new RemoteContentRequest($entry['content']);
+          $context->getHttpFetcher()->fetch($request, $context);
+          if ($request->getHttpCode() == '200') {
+            $ret .= $request->getResponseContent()."\n";
+          }
+          break;
+        case 'FILE':
+          $file = $feature['basePath'] . '/' . $entry['content'];
+          // add jsmin compression here
+          $ret .= file_get_contents($file). "\n";
+          break;
+        case 'INLINE':
+          $ret .= $entry['content'] . "\n";
+          break;
       }
-      $this->features[$name] = $entry;
-      $this->validateFeatureGraph();
     }
-    return $entry;
+    return $ret;
   }
 
-  private function validateFeatureGraph() {  // TODO: ensure that features form a DAG and that all deps are provided
-  }
-
-  public function getAllFeatures() {
-    return $this->features;
-  }
-
-  public function getIncludedFeatures($needed, &$resultsFound, &$resultsMissing) {
+  public function resolveFeatures($needed, &$resultsFound, &$resultsMissing) {
     $resultsFound = array();
     $resultsMissing = array();
     if (! count($needed)) {
       // Shortcut for gadgets that don't have any explicit dependencies.
-      $resultsFound = $this->core;
+      $resultsFound = $this->coreFeatures;
       return true;
     }
     foreach ($needed as $featureName) {
@@ -90,50 +86,141 @@
   }
 
   private function addFeatureToResults(&$results, $feature) {
-    if (in_array($feature->name, $results)) {
+    if (in_array($feature['name'], $results)) {
       return;
     }
-    foreach ($feature->deps as $dep) {
-      //TODO: Temporal fix, double check where empty dependencies are being added
-      if (! empty($dep)) {
-        $this->addFeatureToResults($results, $this->features[$dep]);
-      }
+    foreach ($feature['deps'] as $dep) {
+      $this->addFeatureToResults($results, $this->features[$dep]);
+    }
+    if (!in_array($feature['name'], $results)) {
+      $results[] = $feature['name'];
     }
-    $results[$feature->name] = $feature->name;
-  }
-
-  public function getEntry($name) {
-    return $this->features[$name];
   }
-}
-
-// poor man's namespacing
-class GadgetFeatureRegistryEntry {
-  public $name;
-  public $deps = array();
-  public $feature;
 
-  public function __construct($name, $deps, $feature, $registry) {
-    $this->name = $name;
-    if ($deps != null) {
-      foreach ($deps as $dep) {
-        $entry = $registry->getEntry($dep);
-        $this->deps[$entry->name] = $entry->name;
+  /**
+   * Loads the features present in the $featurePath
+   *
+   * @param string $featurePath path to scan
+   */
+  private function registerFeatures($featurePath) {
+    $this->features = array();
+    // Load the features from the shindig/features/features.txt file
+    $featuresFile = $featurePath . '/features.txt';
+    if (File::exists($featuresFile)) {
+      $files = explode("\n", file_get_contents($featuresFile));
+      // custom sort, else core.io seems to bubble up before core, which breaks the dep chain order
+      usort($files, array($this, 'sortFeaturesFiles'));
+      foreach ($files as $file) {
+        if (! empty($file) && strpos($file, 'feature.xml') !== false && substr($file, 0, 1) != '#' && substr($file, 0, 2) != '//') {
+          $file = realpath($featurePath . '/../' . trim($file));
+          $feature = $this->processFile($file);
+          $this->features[$feature['name']] = $feature;
+        }
+      }
+    }
+    // Determine the core features
+    $this->coreFeatures = array();
+    foreach ($this->features as $entry) {
+      if (strtolower(substr($entry['name'], 0, strlen('core'))) == 'core') {
+        $this->coreFeatures[$entry['name']] = $entry['name'];
+      }
+    }
+    // And make sure non-core features depend on core.
+    foreach ($this->features as $key => $entry) {
+      if (strtolower(substr($entry['name'], 0, strlen('core'))) != 'core') {
+        $this->features[$key]['deps'] = array_merge($entry['deps'], $this->coreFeatures);
+      }
+    }
+  }
+
+  /**
+   * Loads the feature's xml content
+   *
+   * @param unknown_type $file
+   * @return unknown
+   */
+  private function processFile($file) {
+    $feature = null;
+    if (File::exists($file)) {
+      if (($content = file_get_contents($file))) {
+        $feature = $this->parse($content, dirname($file));
+      }
+    }
+    return $feature;
+  }
+
+  /**
+   * Parses the feature's XML content
+   *
+   * @param string $content
+   * @param string $path
+   * @return feature array
+   */
+  private function parse($content, $path) {
+    $doc = simplexml_load_string($content);
+    $feature = array();
+    $feature['deps'] = array();
+    $feature['basePath'] = $path;
+    if (! isset($doc->name)) {
+      throw new GadgetException('Invalid name in feature: ' . $path);
+    }
+    $feature['name'] = trim($doc->name);
+    foreach ($doc->gadget as $gadget) {
+      $this->processContext($feature, $gadget, false);
+    }
+    foreach ($doc->container as $container) {
+      $this->processContext($feature, $container, true);
+    }
+    foreach ($doc->dependency as $dependency) {
+      $feature['deps'][trim($dependency)] = trim($dependency);
+    }
+    return $feature;
+  }
+
+  /**
+   * Processes the feature's entries
+   *
+   * @param array $feature
+   * @param string $context
+   * @param boolean $isContainer
+   */
+  private function processContext(&$feature, $context, $isContainer) {
+    foreach ($context->script as $script) {
+      $attributes = $script->attributes();
+      if (! isset($attributes['src'])) {
+        // inline content
+        $type = 'INLINE';
+        $content = (string)$script;
+      } else {
+        $content = trim($attributes['src']);
+        if (strtolower(substr($content, 0, strlen("http://"))) == "http://" || strtolower(substr($content, 0, strlen("https://"))) == "https://") {
+          $type = 'URL';
+        } else {
+          $type = 'FILE';
+          // skip over any java resource files (res://) since we don't support them
+          if (substr($content, 0, 6) == 'res://') {
+            continue;
+          }
+          $content = $content;
+        }
+      }
+      $library = array('type' => $type, 'content' => $content);
+      if ($library != null) {
+        if ($isContainer) {
+          $feature['containerJs'][] = $library;
+        } else {
+          $feature['gadgetJs'][] = $library;
+        }
       }
     }
-    $this->feature = $feature;
-  }
-
-  public function getName() {
-    return $this->name;
-  }
-
-  public function getDependencies() {
-    return $this->deps;
   }
 
-  public function getFeature() {
-    return $this->feature;
+  private function sortFeaturesFiles($feature1, $feature2) {
+    $feature1 = basename(str_replace('/feature.xml', '', $feature1));
+    $feature2 = basename(str_replace('/feature.xml', '', $feature2));
+    if ($feature1 == $feature2) {
+      return 0;
+    }
+    return ($feature1 < $feature2) ? - 1 : 1;
   }
 }
-

Added: incubator/shindig/trunk/php/src/gadgets/GadgetSpec.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetSpec.php?rev=744702&view=auto
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetSpec.php (added)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetSpec.php Sun Feb 15 17:35:05 2009
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+class GadgetSpec {
+  /**
+   * MD5 checksum of the xml's content
+   *
+   * @var string
+   */
+  public $checksum;
+
+  // Basic and extended ModulePrefs attributes
+  public $title;
+  public $author;
+  public $authorEmail;
+  public $description;
+  public $directoryTitle;
+  public $screenshot;
+  public $thumbnail;
+  public $titleUrl;
+  public $authorAffiliation;
+  public $authorLocation;
+  public $authorPhoto;
+  public $authorAboutme;
+  public $authorQuote;
+  public $authorLink;
+  public $showStats;
+  public $showInDirectory;
+  public $string;
+  public $width;
+  public $height;
+  public $category;
+  public $category2;
+  public $singleton;
+  public $renderInline;
+  public $scaling;
+  public $scrolling;
+
+  public $preloads;
+  public $locales;
+  public $icon;
+  public $optionalFeatures;
+  public $requiredFeatures;
+
+  public $links;
+  public $userPrefs;
+}

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php?rev=744702&r1=744701&r2=744702&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php Sun Feb 15 17:35:05 2009
@@ -18,248 +18,223 @@
  * under the License.
  */
 
-class SpecParserException extends Exception {
+class GadgetSpecException extends Exception {
 }
 
+/**
+ * Parses the XML content into a GadgetSpec object
+ */
 class GadgetSpecParser {
-
-  public function parse($xml, $context) {
-    if (empty($xml)) {
-      throw new SpecParserException("Empty XML document");
-    }
-    // make sure we can generate a detailed error report
+  /**
+   * Parses the $xmlContent into a Gadget class
+   *
+   * @param string $xmlContent
+   */
+  public function parse($xmlContent) {
     libxml_use_internal_errors(true);
-    if (($doc = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)) == false) {
-      $errors = @libxml_get_errors();
-      $xmlErrors = '';
+    $doc = new DOMDocument();
+    if (! $doc->loadXML($xmlContent, LIBXML_NOCDATA)) {
+      $errors = libxml_get_errors();
+      $errorStr = '';
       foreach ($errors as $error) {
-        $xmlErrors .= $this->displayXmlError($error);
-      }
-      @libxml_clear_errors();
-      throw new SpecParserException("<b>Invalid XML Document</b><br/>\n" . $xmlErrors);
-    }
-    if (count($doc->ModulePrefs) != 1) {
-      throw new SpecParserException("Missing or duplicated <ModulePrefs>");
-    }
-    $gadget = new Gadget($context->getGadgetId(), $context);
-    // record Checksum to trace xml version
-    $gadget->setChecksum($xml);
-    // process ModulePref attributes
-    $this->processModulePrefs($gadget, $doc->ModulePrefs);
-    if (isset($doc->ModulePrefs->OAuth)) {
-      // process OAuthPref attributes
-      $this->processOAuthSpec($gadget, $doc->ModulePrefs->OAuth, $context);
-    }
-    // process UserPrefs, if any
-    foreach ($doc->UserPref as $pref) {
-      $this->processUserPref($gadget, $pref);
-    }
-    // Assume gadget v1
-    $explicit_profile = false;
-    foreach ($doc->Content as $content) {
-      $attributes = $content->attributes();
-      if (empty($attributes['type'])) {
-        throw new SpecParserException("No content type specified!");
-      }
-      $view = isset($attributes['view']) ? trim($attributes['view']) : '';
-      // Note if we find a profile explicity defined
-      if (strpos($view, "profile") !== false) {
-        $explicit_profile = true;
-      }
-    }
-    foreach ($doc->Content as $content) {
-      $attributes = $content->attributes();
-      if (empty($attributes['type'])) {
-        throw new SpecParserException("No content type specified!");
-      }
-      $view = isset($attributes['view']) ? trim($attributes['view']) : '';
-      // If view isnt defined and we didnt find a profile explicity defined
-      if ($view == '') {
-        if (! $explicit_profile) {
-          $this->processContent($gadget, $content, array(0 => DEFAULT_VIEW));
-        }
-        // If view isnt defined and a profile was found, this will catch it
-      } else {
-        $views = explode(',', $view);
-        $this->processContent($gadget, $content, $views);
+        $errorStr .= $error . " \n";
       }
+      libxml_clear_errors();
+      throw new GadgetSpecException("Error parsing gadget xml:\n$errorStr");
     }
-    foreach ($doc->ModulePrefs->Preload as $feature) {
-      $gadget->preloads[] = new Preload($feature);
-    }
-    foreach ($doc->ModulePrefs->Require as $feature) {
-      $this->processFeature($gadget, $feature, true);
-    }
-    foreach ($doc->ModulePrefs->Optional as $feature) {
-      $this->processFeature($gadget, $feature, false);
-    }
-    foreach ($doc->ModulePrefs->Icon as $icon) {
-      $this->processIcon($gadget, $icon);
-    }
+    //TODO: we could do a XSD schema validation here, but both the schema and most of the gadgets seem to have bugs, so it's not really practical yet (and slow)
+    // $doc->schemaValidate('gadget.xsd');
+    $gadget = new GadgetSpec();
+    $gadget->checksum = md5($xmlContent);
+    $this->parseModulePrefs($doc, $gadget);
+    $this->parseLinks($doc, $gadget);
+    $this->parseUserPrefs($doc, $gadget);
+    $this->parseViews($doc, $gadget);
+    //TODO
+    // OAuthService / OAuthSpec
+    // PipelinedData
     return $gadget;
   }
 
-  private function processModulePrefs(&$gadget, $ModulePrefs) {
-    $attributes = $ModulePrefs->attributes();
-    if (empty($attributes['title'])) {
-      throw new SpecParserException("Missing or empty \"title\" attribute.");
-    }
-    // Get ModulePrefs base and extended attributes
-    // See http://code.google.com/apis/gadgets/docs/gadgets-extended-xsd.html
-    // (trim is used here since it not only cleans up the text, but also auto-casts the SimpleXMLElement to a string)
-    $gadget->title = trim($attributes['title']);
-    $gadget->author = isset($attributes['author']) ? trim($attributes['author']) : '';
-    $gadget->authorEmail = isset($attributes['author_email']) ? trim($attributes['author_email']) : '';
-    $gadget->description = isset($attributes['description']) ? trim($attributes['description']) : '';
-    $gadget->directoryTitle = isset($attributes['directory_title']) ? trim($attributes['directory_title']) : '';
-    $gadget->screenshot = isset($attributes['screenshot']) ? trim($attributes['screenshot']) : '';
-    $gadget->thumbnail = isset($attributes['thumbnail']) ? trim($attributes['thumbnail']) : '';
-    $gadget->titleUrl = isset($attributes['title_url']) ? trim($attributes['title_url']) : '';
-    // Extended spec fields
-    $gadget->authorAffiliation = isset($attributes['author_affiliation']) ? trim($attributes['author_affiliation']) : '';
-    $gadget->authorLocation = isset($attributes['author_location']) ? trim($attributes['author_location']) : '';
-    $gadget->authorPhoto = isset($attributes['author_photo']) ? trim($attributes['author_photo']) : '';
-    $gadget->authorAboutMe = isset($attributes['author_aboutme']) ? trim($attributes['author_aboutme']) : '';
-    $gadget->authorQuote = isset($attributes['author_quote']) ? trim($attributes['author_quote']) : '';
-    $gadget->authorLink = isset($attributes['author_link']) ? trim($attributes['author_link']) : '';
-    $gadget->showStats = isset($attributes['show_stats']) ? trim($attributes['show_stats']) : '';
-    $gadget->showInDirectory = isset($attributes['show_in_directory']) ? trim($attributes['show_in_directory']) : '';
-    $gadget->string = isset($attributes['string']) ? trim($attributes['string']) : '';
-    $gadget->width = isset($attributes['width']) ? trim($attributes['width']) : '';
-    $gadget->height = isset($attributes['height']) ? trim($attributes['height']) : '';
-    $gadget->category = isset($attributes['category']) ? trim($attributes['category']) : '';
-    $gadget->category2 = isset($attributes['category2']) ? trim($attributes['category2']) : '';
-    $gadget->singleton = isset($attributes['singleton']) ? trim($attributes['singleton']) : '';
-    $gadget->renderInline = isset($attributes['render_inline']) ? trim($attributes['render_inline']) : '';
-    $gadget->scaling = isset($attributes['scaling']) ? trim($attributes['scaling']) : '';
-    $gadget->scrolling = isset($attributes['scrolling']) ? trim($attributes['scrolling']) : '';
-    foreach ($ModulePrefs->Link as $link) {
-      $gadget->links[] = $this->processLink($link);
-    }
-    foreach ($ModulePrefs->Locale as $locale) {
-      $gadget->localeSpecs[] = $this->processLocale($locale);
-    }
-  }
-
-  private function processLink($link) {
-    $attributes = $link->attributes();
-    $rel = isset($attributes['rel']) ? trim($attributes['rel']) : '';
-    $href = isset($attributes['href']) ? trim($attributes['href']) : '';
-    $method = isset($attributes['method']) ? trim($attributes['method']) : 'GET';
-    $link = new LinkSpec($rel, $href, $method);
-    return $link;
-  }
-
-  private function processLocale($locale) {
-    $attributes = $locale->attributes();
-    $messageAttr = isset($attributes['messages']) ? trim($attributes['messages']) : '';
-    $languageAttr = isset($attributes['lang']) ? trim($attributes['lang']) : 'all';
-    $countryAttr = isset($attributes['country']) ? trim($attributes['country']) : 'all';
-    $rtlAttr = isset($attributes['language_direction']) ? trim($attributes['language_direction']) : '';
-    $rightToLeft = $rtlAttr == 'rtl';
-    $localeMessageBundles = array();
-    if ($messageAttr == '') {
-      $parser = new MessageBundleParser();
-      $localeMessageBundles = $parser->getMessages($locale);
-    }
-    $locale = new LocaleSpec();
-    $locale->rightToLeft = $rightToLeft;
-    $locale->url = $messageAttr;
-    $locale->localeMessageBundles = $localeMessageBundles;
-    $locale->locale = new Locale($languageAttr, $countryAttr);
-    return $locale;
-  }
-
-  private function processUserPref(&$gadget, $pref) {
-    $attributes = $pref->attributes();
-    $preference = new UserPref();
-    if (empty($attributes['name'])) {
-      throw new SpecParserException("All UserPrefs must have name attributes.");
-    }
-    $preference->name = trim($attributes['name']);
-    $preference->displayName = isset($attributes['display_name']) ? $gadget->getSubstitutions()->substitute(trim($attributes['display_name'])) : '';
-    // if its set -and- in our valid 'enum' of types, use it, otherwise assume STRING, to try and emulate java's enum behavior
-    $preference->required = isset($attributes['required']) ? $gadget->getSubstitutions()->substitute(trim($attributes['required'])) : 'false';
-    $preference->dataType = isset($attributes['datatype']) && in_array(strtoupper($attributes['datatype']), $preference->DataTypes) ? strtoupper($attributes['datatype']) : 'STRING';
-    $preference->defaultValue = isset($attributes['default_value']) ? $gadget->getSubstitutions()->substitute(trim($attributes['default_value'])) : '';
-    if (isset($pref->EnumValue)) {
-      foreach ($pref->EnumValue as $enum) {
-        $attr = $enum->attributes();
-        $valueText = trim($attr['value']);
-        $displayText = ! empty($attr['display_value']) ? $gadget->getSubstitutions()->substitute(trim($attr['display_value'])) : $valueText;
-        $preference->enumValues[$valueText] = $displayText;
-      }
-    }
-    $gadget->userPrefs[] = $preference;
-  }
-
-  private function processContent(&$gadget, $content, $views) {
-    $html = (string)$content; // no trim here since empty lines can have structural meaning, so typecast to string instead
-    foreach ($views as $view) {
-      $viewSpec = new ViewSpec($view, $content);
-      if (! isset($gadget->views[$view])) {
-        $viewSpec->content = $html;
-        $gadget->views[$view] = $viewSpec;
-      } else {
-        if ($gadget->views[$view]->getName() == $viewSpec->getName() && $viewSpec->getType() != $gadget->views[$view]->getType()) {
-          throw new SpecParserException("You may not mix content " . " types in the same view.");
+  /**
+   * Parse the gadget views
+   *
+   * @param DOMDocument $doc
+   * @param GadgetSpec $gadget
+   */
+  private function parseViews(DOMDocument &$doc, GadgetSpec &$gadget) {
+    $views = $doc->getElementsByTagName('Content');
+    if (! $views || $views->length < 1) {
+      throw new GadgetSpecException("A gadget needs to have at least one view");
+    }
+    $gadget->views = array();
+    foreach ($views as $viewNode) {
+      if ($viewNode->getAttribute('type' == 'url') && $viewNode->getAttribute('href') == null) {
+        throw new GadgetSpecException("Malformed <Content> href value");
+      }
+      foreach (explode(',', $viewNode->getAttribute('view')) as $view) {
+        $view = trim($view);
+        if (isset($gadget->views[$view])) {
+          $gadget->views[$view]['content'] .= $viewNode->nodeValue;
+        } else {
+          $gadget->views[$view] = array('view' => $view, 'type' => strtoupper($viewNode->getAttribute('type')), 'href' => $viewNode->getAttribute('href'), 'preferedHeight' => $viewNode->getAttribute('prefered_height'),
+              'preferedWidth' => $viewNode->getAttribute('prefered_width'), 'quirks' => $viewNode->getAttribute('quirks'), 'content' => $viewNode->nodeValue);
         }
-        $gadget->views[$view]->addContent($html);
       }
     }
   }
 
-  private function processFeature(&$gadget, $feature, $required) {
-    $featureSpec = new FeatureSpec();
-    $attributes = $feature->attributes();
-    if (empty($attributes['feature'])) {
-      throw new SpecParserException("Feature not specified in <" . ($required ? "Required" : "Optional") . "> tag");
-    }
-    $featureSpec->name = trim($attributes['feature']);
-    $featureSpec->optional = ! $required;
-    foreach ($feature->Param as $param) {
-      $attr = $param->attributes();
-      if (empty($attr['name'])) {
-        throw new SpecParserException("Missing name attribute in <Param>.");
+  /**
+   * Parses the UserPref entries
+   *
+   * @param DOMDocument $doc
+   * @param GadgetSpec $gadget
+   */
+  private function parseUserPrefs(DOMDocument &$doc, GadgetSpec &$gadget) {
+    $gadget->userPrefs = array();
+    if (($userPrefs = $doc->getElementsByTagName('UserPref')) != null) {
+      foreach ($userPrefs as $prefNode) {
+        $pref = array('name' => $prefNode->getAttribute('name'), 'displayName' => $prefNode->getAttribute('display_name'), 'datatype' => strtoupper($prefNode->getAttribute('datatype')), 'defaultValue' => $prefNode->getAttribute('default_value'),
+            'required' => $prefNode->getAttribute('required'));
+        if ($pref['datatype'] == 'ENUM') {
+          if (($enumValues = $prefNode->getElementsByTagName('EnumValue')) != null) {
+            $enumVals = array();
+            foreach ($enumValues as $enumNode) {
+              $enumVals[] = array('value' => $enumNode->getAttribute('value'), 'displayValue' => $enumNode->getAttribute('display_value'));
+            }
+          }
+          $pref['enumValues'] = $enumVals;
+        }
+        $gadget->userPrefs[] = $pref;
       }
-      $name = trim($attr['name']);
-      $value = trim($param);
-      $featureSpec->params[$name] = $value;
     }
-    $gadget->requires[$featureSpec->name] = $featureSpec;
-  }
-
-  private function processIcon(Gadget &$gadget, $icon) {
-    $attributes = $icon->attributes();
-    $iconSpec = new Icon();
-    $iconSpec->content = (string)(trim($icon));
-    $iconSpec->mode = isset($attributes['mode']) ? trim($attributes['mode']) : '';
-    $iconSpec->type = isset($attributes['type']) ? trim($attributes['type']) : '';
-    $gadget->icons[] = $iconSpec;
   }
 
-  private function processOAuthSpec(Gadget &$gadget, $OAuthSpec) {
-    $oauthSpec = new OAuthSpec($OAuthSpec->Service);
-    $gadget->setOAuthSpec($oauthSpec);
-  }
-
-  private function displayXmlError($error) {
-    $return = '';
-    switch ($error->level) {
-      case LIBXML_ERR_WARNING:
-        $return .= "Warning [{$error->code}]: ";
-        break;
-      case LIBXML_ERR_ERROR:
-        $return .= "Error [{$error->code}]: ";
-        break;
-      case LIBXML_ERR_FATAL:
-        $return .= "Fatal Error [{$error->code}]: ";
-        break;
-    }
-    $return .= trim($error->message) . ", Line: {$error->line}" . " Column: {$error->column}";
-    if ($error->file) {
-      $return .= "File: {$error->file}";
+  /**
+   * Parses the link spec elements
+   *
+   * @param DOMDocument $doc
+   * @param GadgetSpec $gadget
+   */
+  private function parseLinks(DOMDocument &$doc, GadgetSpec &$gadget) {
+    $gadget->links = array();
+    if (($links = $doc->getElementsByTagName('link')) != null) {
+      foreach ($links as $linkNode) {
+        $gadget->links[] = array('rel' => $linkNode->getAttribute('rel'), 'href' => $linkNode->getAttribute('href'), 'method' => strtoupper($linkNode->getAttribute('method')));
+      }
+    }
+  }
+
+  /**
+   * Parses the ModulePrefs section of the xml structure. The ModulePrefs
+   * section is required, so if it's missing or if there's 2 an GadgetSpecException will be thrown.
+   *
+   * This function also parses the ModulePref's child elements (Icon, Features, Preload and Locale)
+   *
+   * @param DOMDocument $doc
+   */
+  private function parseModulePrefs(DOMDocument &$doc, GadgetSpec &$gadget) {
+    $modulePrefs = $doc->getElementsByTagName("ModulePrefs");
+    if ($modulePrefs->length < 1) {
+      throw new GadgetSpecException("Missing ModulePrefs block");
+    } elseif ($modulePrefs->length > 1) {
+      throw new GadgetSpecException("More then one ModulePrefs block found");
+    }
+    $modulePrefs = $modulePrefs->item(0);
+    // parse the ModulePrefs attributes
+    $knownAttributes = array('title', 'author', 'authorEmail', 'description', 'directoryTitle', 'screenshot', 'thumbnail', 'titleUrl', 'authorAffiliation', 'authorLocation', 'authorPhoto', 'authorAboutme', 'authorQuote', 'authorLink', 'showStats',
+        'showInDirectory', 'string', 'width', 'height', 'category', 'category2', 'singleton', 'renderInline', 'scaling', 'scrolling');
+    foreach ($modulePrefs->attributes as $key => $attribute) {
+      $attrValue = trim($attribute->value);
+      // var format conversion from directory_title => directoryTitle
+      $attrKey = str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
+      $attrKey[0] = strtolower($attrKey[0]);
+      if (in_array($attrKey, $knownAttributes)) {
+        $gadget->$attrKey = $attrValue;
+      }
+    }
+    // And parse the child nodes
+    $this->parseIcon($modulePrefs, $gadget);
+    $this->parseFeatures($modulePrefs, $gadget);
+    $this->parsePreloads($modulePrefs, $gadget);
+    $this->parseLocales($modulePrefs, $gadget);
+  }
+
+  /**
+   * Parses the (optional) Icon element, returns a Icon class or null
+   *
+   * @param DOMElement $modulePrefs
+   * @param Gadget $gadget
+   */
+  private function parseIcon(DOMElement &$modulePrefs, GadgetSpec &$gadget) {
+    if (($iconNodes = $modulePrefs->getElementsByTagName('Icon')) != null) {
+      if ($iconNodes->length > 1) {
+        throw new GadgetSpecException("A gadget can only have one Icon element");
+      } elseif ($iconNodes->length == 1) {
+        $icon = $iconNodes->item(0);
+        $gadget->icon = $icon->nodeValue;
+      }
+    }
+  }
+
+  /**
+   * Parses the Required and Optional feature entries in the ModulePrefs
+   *
+   * @param DOMElement $modulePrefs
+   * @param Gadget $gadget
+   */
+  private function parseFeatures(DOMElement &$modulePrefs, GadgetSpec &$gadget) {
+    $gadget->requiredFeatures = $gadget->optionalFeatures = array();
+    if (($requiredNodes = $modulePrefs->getElementsByTagName('Require')) != null) {
+      foreach ($requiredNodes as $requiredFeature) {
+        $gadget->requiredFeatures[] = $requiredFeature->getAttribute('feature');
+      }
+    }
+    if (($optionalNodes = $modulePrefs->getElementsByTagName('Optional')) != null) {
+      foreach ($optionalNodes as $optionalFeature) {
+        $gadget->optionalFeatures[] = $optionalFeature->getAttribute('feature');
+      }
+    }
+  }
+
+  /**
+   * Parses the preload elements
+   *
+   * @param DOMElement $modulePrefs
+   * @param Gadget $gadget
+   */
+  private function parsePreloads(DOMElement &$modulePrefs, GadgetSpec &$gadget) {
+    $gadget->preloads = array();
+    if (($preloadNodes = $modulePrefs->getElementsByTagName('Preload')) != null) {
+      foreach ($preloadNodes as $node) {
+        $gadget->preloads[] = array('href' => $node->getAttribute('href'), 'authz' => strtoupper($node->getAttribute('authz')), 'signViewer' => $node->getAttribute('sign_viewer'), 'signOwner' => $node->getAttribute('sign_owner'));
+      }
+    }
+  }
+
+  /**
+   * Parses the Locale (message bundle) entries
+   *
+   * @param DOMElement $modulePrefs
+   * @param Gadget $gadget
+   */
+  private function parseLocales(DOMElement &$modulePrefs, GadgetSpec &$gadget) {
+    $gadget->locales = array();
+    if (($localeNodes = $modulePrefs->getElementsByTagName('Locale')) != null) {
+      foreach ($localeNodes as $node) {
+        $messageBundle = array();
+        if (($messageBundleNode = $node->getElementsByTagName('messagebundle')) != null && $messageBundleNode->length > 0) {
+          // parse inlined messages
+          $messageBundleNode = $messageBundleNode->item(0);
+          $messages = $messageBundleNode->getElementsByTagName('msg');
+          foreach ($messages as $msg) {
+            $messageBundle[$msg->getAttribute('name')] = trim($msg->nodeValue);
+          }
+        }
+        $lang = $node->getAttribute('lang') == '' ? 'all' : strtolower($node->getAttribute('lang'));
+        $country = $node->getAttribute('country') == '' ? 'all' : strtoupper($node->getAttribute('country'));
+        $gadget->locales[] = array('lang' => $lang, 'country' => $country, 'messages' => $node->getAttribute('messages'), 'languageDirection' => $node->getAttribute('language_direction'), 'messageBundle' => $messageBundle);
+      }
     }
-    return "$return\n";
   }
 }