Rolling back the patch from SHINDIG-966, was breaking makeRequest
[shindig.git] / php / src / gadgets / GadgetFeatureRegistry.php
blob6bb22c4b82dd2693755cfad27c9cf1a5eac1d0bb
1 <?php
3 /**
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
22 /**
23 * Class that deals with the processing, loading and dep resolving of the gadget features
24 * Features are javascript libraries that provide an API, like 'opensocial' or 'settitle'
27 class GadgetFeatureRegistry {
28 public $features;
29 private $coreDone = false;
30 private $coreFeaturs;
32 public function __construct($featurePath) {
33 $this->registerFeatures($featurePath);
36 public function getFeatureContent($feature, GadgetContext $context, $isGadgetContext) {
37 if (!isset($this->features[$feature])) {
38 throw new GadgetException("Invalid feature: ".htmlentities($feature));
40 $featureName = $feature;
41 $feature = $this->features[$feature];
42 $filesContext = $isGadgetContext ? 'gadgetJs' : 'containerJs';
43 if (!isset($feature[$filesContext])) {
44 // no javascript specified for this context
45 return '';
47 $ret = '';
48 if (Config::get('compress_javascript')) {
49 $featureCache = Cache::createCache(Config::get('feature_cache'), 'FeatureCache');
50 if (($featureContent = $featureCache->get(md5('features:'.$featureName.$isGadgetContext)))) {
51 return $featureContent;
54 foreach ($feature[$filesContext] as $entry) {
55 switch ($entry['type']) {
56 case 'URL':
57 $request = new RemoteContentRequest($entry['content']);
58 $context->getHttpFetcher()->fetch($request, $context);
59 if ($request->getHttpCode() == '200') {
60 $ret .= $request->getResponseContent()."\n";
62 break;
63 case 'FILE':
64 $file = $feature['basePath'] . '/' . $entry['content'];
65 $ret .= file_get_contents($file). "\n";
66 break;
67 case 'INLINE':
68 $ret .= $entry['content'] . "\n";
69 break;
72 if (Config::get('compress_javascript')) {
73 $ret = JsMin::minify($ret);
74 $featureCache->set(md5('features:'.$featureName.$isGadgetContext), $ret);
76 return $ret;
79 public function resolveFeatures($needed, &$resultsFound, &$resultsMissing) {
80 $resultsFound = array();
81 $resultsMissing = array();
82 if (! count($needed)) {
83 // Shortcut for gadgets that don't have any explicit dependencies.
84 $resultsFound = $this->coreFeatures;
85 return true;
87 foreach ($needed as $featureName) {
88 $feature = isset($this->features[$featureName]) ? $this->features[$featureName] : null;
89 if ($feature == null) {
90 $resultsMissing[] = $featureName;
91 } else {
92 $this->addFeatureToResults($resultsFound, $feature);
95 return count($resultsMissing) == 0;
98 private function addFeatureToResults(&$results, $feature) {
99 if (in_array($feature['name'], $results)) {
100 return;
102 foreach ($feature['deps'] as $dep) {
103 $this->addFeatureToResults($results, $this->features[$dep]);
105 if (!in_array($feature['name'], $results)) {
106 $results[] = $feature['name'];
111 * Loads the features present in the $featurePath
113 * @param string $featurePath path to scan
115 private function registerFeatures($featurePath) {
116 $this->features = array();
117 // Load the features from the shindig/features/features.txt file
118 $featuresFile = $featurePath . '/features.txt';
119 if (File::exists($featuresFile)) {
120 $files = explode("\n", file_get_contents($featuresFile));
121 // custom sort, else core.io seems to bubble up before core, which breaks the dep chain order
122 usort($files, array($this, 'sortFeaturesFiles'));
123 foreach ($files as $file) {
124 if (! empty($file) && strpos($file, 'feature.xml') !== false && substr($file, 0, 1) != '#' && substr($file, 0, 2) != '//') {
125 $file = realpath($featurePath . '/../' . trim($file));
126 $feature = $this->processFile($file);
127 $this->features[$feature['name']] = $feature;
131 // Determine the core features
132 $this->coreFeatures = array();
133 foreach ($this->features as $entry) {
134 if (strtolower(substr($entry['name'], 0, strlen('core'))) == 'core') {
135 $this->coreFeatures[$entry['name']] = $entry['name'];
138 // And make sure non-core features depend on core.
139 foreach ($this->features as $key => $entry) {
140 if (strtolower(substr($entry['name'], 0, strlen('core'))) != 'core') {
141 $this->features[$key]['deps'] = array_merge($entry['deps'], $this->coreFeatures);
147 * Loads the feature's xml content
149 * @param unknown_type $file
150 * @return unknown
152 private function processFile($file) {
153 $feature = null;
154 if (File::exists($file)) {
155 if (($content = file_get_contents($file))) {
156 $feature = $this->parse($content, dirname($file));
159 return $feature;
163 * Parses the feature's XML content
165 * @param string $content
166 * @param string $path
167 * @return feature array
169 private function parse($content, $path) {
170 $doc = simplexml_load_string($content);
171 $feature = array();
172 $feature['deps'] = array();
173 $feature['basePath'] = $path;
174 if (! isset($doc->name)) {
175 throw new GadgetException('Invalid name in feature: ' . $path);
177 $feature['name'] = trim($doc->name);
178 foreach ($doc->gadget as $gadget) {
179 $this->processContext($feature, $gadget, false);
181 foreach ($doc->container as $container) {
182 $this->processContext($feature, $container, true);
184 foreach ($doc->dependency as $dependency) {
185 $feature['deps'][trim($dependency)] = trim($dependency);
187 return $feature;
191 * Processes the feature's entries
193 * @param array $feature
194 * @param string $context
195 * @param boolean $isContainer
197 private function processContext(&$feature, $context, $isContainer) {
198 foreach ($context->script as $script) {
199 $attributes = $script->attributes();
200 if (! isset($attributes['src'])) {
201 // inline content
202 $type = 'INLINE';
203 $content = (string)$script;
204 } else {
205 $content = trim($attributes['src']);
206 if (strtolower(substr($content, 0, strlen("http://"))) == "http://" || strtolower(substr($content, 0, strlen("https://"))) == "https://") {
207 $type = 'URL';
208 } else {
209 $type = 'FILE';
210 // skip over any java resource files (res://) since we don't support them
211 if (substr($content, 0, 6) == 'res://') {
212 continue;
214 $content = $content;
217 $library = array('type' => $type, 'content' => $content);
218 if ($library != null) {
219 if ($isContainer) {
220 $feature['containerJs'][] = $library;
221 } else {
222 $feature['gadgetJs'][] = $library;
228 private function sortFeaturesFiles($feature1, $feature2) {
229 $feature1 = basename(str_replace('/feature.xml', '', $feature1));
230 $feature2 = basename(str_replace('/feature.xml', '', $feature2));
231 if ($feature1 == $feature2) {
232 return 0;
234 return ($feature1 < $feature2) ? - 1 : 1;