gadget property, and populates Msg, UserPref and ViewParams dataContext * * @param Gadget $gadget */ public function setGadget(Gadget $gadget) { $this->gadget = $gadget; $this->dataContext['UserPrefs'] = $this->dataContext['ViewParams'] = $this->dataContext['Msg'] = array(); if (isset($this->gadget->gadgetSpec->locales)) { foreach ($this->gadget->gadgetSpec->locales as $key => $val) { $this->dataContext['Msg'][$key] = $val; } } if (isset($this->gadget->gadgetSpec->userPrefs)) { foreach ($this->gadget->gadgetSpec->userPrefs as $pref) { $this->dataContext['UserPrefs'][$pref['name']] = isset($pref['value']) ? $pref['value'] : ''; } } if (isset($_GET['view-params'])) { $viewParams = json_decode($_GET['view-params'], true); if ($viewParams != $_GET['view-params'] && $viewParams && is_array($viewParams)) { foreach ($viewParams as $key => $val) { $this->dataContext['ViewParams'][$key] = $val; } } } } /** * If some templates could not be parsed, we paste the back into the html document * so javascript can take care of them * * @param string $content html to parse * @return string */ public function addTemplates($content) { // If $this->gadget->gadgetSpec->templatesDisableAutoProcessing == true, unparsedTemplates will be empty, so the setting is ignored here if (count($this->unparsedTemplates)) { foreach ($this->unparsedTemplates as $key => $val) { $content = str_replace("", $val . "\n", $content); } } return $content; } /** * This function parses the os-template and os-data script tags. * It's of vital importance to call this function *before* the rewriteContent function * since the html/dom parser of the later breaks, mangles and otherwise destroys the * os template/data script tags. So we need to expand the templates to pure html * before we can proceeed to dom parse the resulting document * * @param string $content html to parse * @return string */ public function parseTemplates($content) { $osTemplates = array(); $osDataRequests = array(); // First extract all the os-data tags, and execute those in a single combined request, saves latency // and is consistent with other server implementations preg_match_all('/(]*type="text\/(os-data)"[^>]*>)(.*)(<\/script>)/imsxU', $content, $osDataRequests); $osDataRequestsCombined = ''; foreach ($osDataRequests[0] as $match) { $osDataRequestsCombined .= $match . "\n"; // Remove the reference from the html document $content = str_replace($match, '', $content); } if (! empty($osDataRequestsCombined)) { $this->performDataRequests($osDataRequestsCombined); } preg_match_all('/(]*type="text\/(os-template)"[^>]*>)(.*)(<\/script>)/imxsU', $content, $osTemplates); $templateLibrary = false; if (count($osTemplates[0])) { // only load the template parser if there's any templates in the gadget content $templateLibrary = new TemplateLibrary($this->gadget->gadgetContext); if ($this->gadget->gadgetSpec->templatesRequireLibraries) { foreach ($this->gadget->gadgetSpec->templatesRequireLibraries as $library) { $templateLibrary->addTemplateLibrary($library); } } foreach ($osTemplates[0] as $match) { if (! $this->gadget->gadgetSpec->templatesDisableAutoProcessing && ($renderedTemplate = $this->renderTemplate($match, $templateLibrary)) !== false) { // Template was rendered, insert the rendered html into the document $content = str_replace($match, $renderedTemplate, $content); } else { /* * The template could not be rendered, this could happen because: * - @require is present, and at least one of the required pieces of data is unavailable * - @name is present * - @autoUpdate == true * - $this->gadget->gadgetSpec->templatesDisableAutoProcessing is set to true * So set a magic marker () that after the dom document parsing will be replaced with the original script content */ $index = count($this->unparsedTemplates); $this->unparsedTemplates[$index] = $match; $content = str_replace($match, "", $content); } } } return $content; } /** * Parses the OpenSocial RPC format data reply into the local data context * * @param array $array the data to add to the context */ public function addContextData($array) { foreach ($array as $val) { // we really only accept entries with a request id, otherwise it can't be referenced by context anyhow if (isset($val['id'])) { $key = $val['id']; // Pick up only the actual data part of the response, so we can do direct variable resolution if (isset($val['result']['list'])) { $this->dataContext[$key] = $val['result']['list']; } elseif (isset($val['result']['entry'])) { $this->dataContext[$key] = $val['result']['entry']; } elseif (isset($val['result'])) { $this->dataContext[$key] = $val['result']; } else { $this->dataContext[$key] = $val; } } } } /** * Parses and performs the (combined) os-data requests * * @param string $osDataRequests */ private function performDataRequests($osDataRequests) { //TODO check with the java implementation guys if they do a caching strategy here (same as with data-pipelining), // would result in a much higher render performance.. libxml_use_internal_errors(true); $this->doc = new \DOMDocument(null, 'utf-8'); $this->doc->preserveWhiteSpace = true; $this->doc->formatOutput = false; $this->doc->strictErrorChecking = false; $this->doc->recover = false; $this->doc->resolveExternals = false; if ($this->doc->loadXML($osDataRequests)) { $dataPipeliningRequests = array(); // walk the one or multiple script tags, and build a combined request array foreach ($this->doc->childNodes as $childNode) { if ($childNode->tagName == 'script') { $dataPipeliningRequests = array_merge($dataPipeliningRequests, DataPipelining::Parse($childNode)); } } // and perform the requests if (count($dataPipeliningRequests)) { $this->dataInserts = DataPipelining::fetch($dataPipeliningRequests, $this->context); $this->addContextData($this->dataInserts); } } else { echo "Error parsing os-data:\n" . XmlError::getErrors($osDataRequests); } } /** * Does the parsing of the template/data script content, then it hands * the os-data parsing to the DataPipeling class, and os-template tags to * the TemplateParser, and then returns the expanded template content (or '' on data) * * @param string $template * @param TemplateLibrary $templateLibrary * @return string */ private function renderTemplate($template, TemplateLibrary $templateLibrary) { libxml_use_internal_errors(true); $this->doc = new \DOMDocument(null, 'utf-8'); $this->doc->preserveWhiteSpace = true; $this->doc->formatOutput = false; $this->doc->strictErrorChecking = false; $this->doc->recover = false; $this->doc->resolveExternals = false; if (! $this->doc->loadXML($template)) { return "Error parsing os-template:\n" . XmlError::getErrors($template); } if ($this->doc->childNodes->length < 1 || $this->doc->childNodes->length >> 1) { return 'Invalid script block'; } $childNode = $this->doc->childNodes->item(0); if ($childNode->tagName == 'script' && $childNode->getAttribute('name') == null && $childNode->getAttribute('autoUpdate') != 'true') { // If the require tag is set, check to see if we have all required data parts, and if not leave it to the client to render if (($require = $childNode->getAttribute('require')) != null) { $requires = explode(',', $require); foreach ($requires as $val) { $val = trim($val); if (! isset($this->dataContext[$val])) { return false; } } } // if $childNode->tag exists, add to global $templateLibraries array, else parse normally $childNodeTag = $childNode->getAttribute('tag'); if (! empty($childNodeTag)) { if (isset($this->templateLibraries[$childNode->getAttribute('tag')])) { throw new ExpressionException("Template " . htmlentities($childNode->getAttribute('tag')) . " was already defined"); } $templateLibrary->addTemplateByNode($childNode); } else { // Everything checked out, proceeding to render the template $parser = new TemplateParser(); $parser->process($childNode, $this->dataContext, $templateLibrary); // unwrap the output, ie we only want the script block's content and not the main node $output = new \DOMDocument(null, 'utf-8'); foreach ($childNode->childNodes as $node) { $outNode = $output->importNode($node, true); $outNode = $output->appendChild($outNode); } // Restore single tags to their html variant, and remove the xml header $ret = str_replace( array('', '
', ''), array('', '
', ''), $output->saveXML()); return $ret; } } return false; } /** * Rewrites the content, based on shindig's configuration (force_rewrite) and/or the gadget's * spec params, it also injects the required html, css and javascript for the final gadget * using the dom observer methods for the head and body * * @param unknown_type $content * @return unknown */ public function rewriteContent($content) { // Rewrite the content, this will rewrite resource links to proxied versions (if requested), sanitize if configured, and // add the various javascript tags to the document $rewriter = new GadgetRewriter($this->context); $rewriter->addObserver('head', $this, 'addHeadTags'); $rewriter->addObserver('body', $this, 'addBodyTags'); return $rewriter->rewrite($content, $this->gadget, true); } /** * Generates the body script content * * @return string script */ public function getBodyScript() { return "gadgets.util.runOnLoadHandlers();"; } /** * Append the runOnLoadHandlers script to the gadget's document body * * @param DOMElement $node * @param DOMDocument $doc */ public function addBodyTags(\DOMElement &$node, \DOMDocument &$doc) { $script = $this->getBodyScript(); $scriptNode = $doc->createElement('script'); $scriptNode->setAttribute('type', 'text/javascript'); $scriptNode->appendChild($doc->createTextNode($script)); $scriptNode->nodeValue = str_replace('&', '&', $script); $node->appendChild($scriptNode); } public function getJavaScripts() { $registry = $this->context->getRegistry(); $forcedJsLibs = $this->getForcedJsLibs(); $forcedAppendJsLibs = Config::get('forcedAppendedJsLibs'); $externFeatures = $forcedJsLibs; $inlineFeatures = array(); foreach ($this->gadget->features as $feature) { if (! in_array($feature, $forcedJsLibs) && ! in_array($feature, $forcedAppendJsLibs)) { $inlineFeatures[] = $feature; } } $sortedExternFeatures = array(); $sortedInlineFeatures = array(); $registry->sortFeatures($externFeatures, $sortedExternFeatures); $registry->sortFeatures($inlineFeatures, $sortedInlineFeatures); // append additional js libs from config to the end of the javascript block // this allows custom overloading of other javascript libraries foreach ($forcedAppendJsLibs as $jsLib) { $sortedInlineFeatures[] = $jsLib; } // if some of the feature libraries are externalized (through a browser cachable