ここの情報は古いです。ご理解頂いた上でお取り扱いください。

source: OpenPNE3/plugins/opOpenSocialPlugin/trunk/lib/vendor/Shindig/src/gadgets/servlet/GadgetRenderingServlet.php @ 8979

Last change on this file since 8979 was 8979, checked in by ShogoKawahara, 12 years ago

updated Shindig library

File size: 15.4 KB
Line 
1<?php
2/*
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 *     http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations under the License.
18 *
19 */
20require 'src/common/HttpServlet.php';
21require 'src/gadgets/GadgetContext.php';
22require 'src/gadgets/GadgetServer.php';
23require 'src/common/RemoteContentRequest.php';
24require 'src/common/RemoteContent.php';
25require 'src/common/Cache.php';
26require 'src/common/RemoteContentFetcher.php';
27require 'src/gadgets/GadgetSpecParser.php';
28require 'src/gadgets/Gadget.php';
29require 'src/gadgets/GadgetId.php';
30require 'src/gadgets/UserPrefs.php';
31require 'src/gadgets/Substitutions.php';
32require 'src/gadgets/LocaleSpec.php';
33require 'src/gadgets/LocaleMessageBundle.php';
34require 'src/common/Locale.php';
35require 'src/gadgets/UserPref.php';
36require 'src/gadgets/ViewSpec.php';
37require 'src/gadgets/FeatureSpec.php';
38require 'src/gadgets/MessageBundleParser.php';
39require 'src/gadgets/MessageBundle.php';
40require 'src/gadgets/GadgetFeatureRegistry.php';
41require 'src/gadgets/GadgetFeatureFactory.php';
42require 'src/gadgets/GadgetFeature.php';
43require 'src/gadgets/JsLibraryFeatureFactory.php';
44require 'src/gadgets/JsLibrary.php';
45require 'src/gadgets/HttpUtil.php';
46require 'src/gadgets/ContainerConfig.php';
47require 'src/gadgets/rewrite/ContentRewriter.php';
48require 'src/gadgets/rewrite/ContentRewriteFeature.php';
49
50/**
51 * This class deals with the gadget rendering requests (in default config this
52 * would be /gadgets/ifr?url=<some gadget's url>). It uses the gadget server and
53 * gadget context to render the xml to a valid html file, and outputs it.
54 *
55 */
56class GadgetRenderingServlet extends HttpServlet {
57        private $context;
58
59        /**
60         * Creates the gadget using the GadgetServer class and calls outputGadget
61         *
62         */
63        public function doGet()
64        {
65                try {
66                        if (empty($_GET['url'])) {
67                                throw new GadgetException("Missing required parameter: url");
68                        }
69                        // GadgetContext builds up all the contextual variables (based on the url or post)
70                        // plus instances all required classes (feature registry, fetcher, blacklist, etc)
71                        $this->context = new GadgetContext('GADGET');
72                        // Unfortunatly we can't do caja content filtering here, hoping we'll have a RPC service
73                        // or command line caja to use for this at some point
74                        $gadgetServer = new GadgetServer();
75                        $gadget = $gadgetServer->processGadget($this->context);
76                        $this->outputGadget($gadget, $this->context);
77                } catch (Exception $e) {
78                        $this->outputError($e);
79                }
80        }
81
82        /**
83         * If an error occured (Exception) this function echo's the Exception's message
84         * and if the config['debug'] is true, shows the debug backtrace in a div
85         *
86         * @param Exception $e the exception to show
87         */
88        private function outputError($e)
89        {
90                header("HTTP/1.0 400 Bad Request", true, 400);
91                echo "<html><body>";
92                echo "<h1>Error</h1>";
93                echo $e->getMessage();
94                if (Config::get('debug')) {
95                        echo "<p><b>Debug backtrace</b></p><div style='overflow:auto; height:300px; border:1px solid #000000'><pre>";
96                        print_r(debug_backtrace());
97                        echo "</pre></div>>";
98                }
99                echo "</body></html>";
100        }
101
102        /**
103         * Takes the gadget to output, and depending on its content type calls either outputHtml-
104         * or outputUrlGadget
105         *
106         * @param Gadget $gadget gadget to render
107         * @param string $view the view to render (only valid with a html content type)
108         */
109        private function outputGadget($gadget, $context)
110        {
111                $view = HttpUtil::getView($gadget, $context);
112                switch ($view->getType()) {
113                        case 'HTML':
114                                $this->outputHtmlGadget($gadget, $context, $view);
115                                break;
116                        case 'URL':
117                                $this->outputUrlGadget($gadget, $context, $view);
118                                break;
119                }
120        }
121
122        /**
123         * Outputs a html content type gadget.
124         * It creates a html page, with the javascripts from the features inline into the page, plus
125         * calls to 'gadgets.config.init' with the container configuration (config/container.js) and
126         * 'gadgets.Prefs.setMessages_' with all the substitutions. For external javascripts it adds
127         * a <script> tag.
128         *
129         * @param Gadget $gadget
130         * @param GadgetContext $context
131         */
132        private function outputHtmlGadget($gadget, $context, $view)
133        {
134                $content = '';
135                $externJs = '';
136                $externFmt = "<script src=\"%s\"></script>";
137                $forcedLibs = $context->getForcedJsLibs();
138                // allow the &libs=.. param to override our forced js libs configuration value
139                if (empty($forcedLibs)) {
140                        $forcedLibs = Config::get('focedJsLibs');
141                }
142                $this->setContentType("text/html; charset=UTF-8");
143                if ($context->getIgnoreCache()) {
144                        // no cache was requested, set non-caching-headers
145                        $this->setNoCache(true);
146                } elseif (isset($_GET['v'])) {
147                        // version was given, cache for a long long time (a year)
148                        $this->setCacheTime(365 * 24 * 60 * 60);
149                } else {
150                        // no version was given, cache for 5 minutes
151                        $this->setCacheTime(5 * 60);
152                }
153                // Was a privacy policy header configured? if so set it
154                if (Config::get('P3P') != '') {
155                        header("P3P: " . Config::get('P3P'));
156                }
157                if (! $view->getQuirks()) {
158                        $content .= "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n";
159                }
160                $content .= "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/><style type=\"text/css\">" . Config::get('gadget_css') . "</style></head><body>\n";
161                // Forced libs first.
162                if (! empty($forcedLibs)) {
163                        $libs = explode(':', $forcedLibs);
164                        echo sprintf($externFmt, Config::get('default_js_prefix') . $this->getJsUrl($libs, $gadget) . "&container=" . $context->getContainer()) . "\n";
165                }
166                $content .= "<script>\n"; 
167               
168                if (! empty($forcedLibs)) {
169                        // if some of the feature libraries are externalized (through a browser cachable <script src="/gadgets/js/opensocial-0.7:settitle.js">
170                        // type url), then we don't want to include dependencies twice, so find the complete features chain, so we can skip over those
171                        $forcedLibsArray = explode(':', $forcedLibs);
172                        $registry = $this->context->getRegistry();
173                        $missing = array();
174                        $registry->getIncludedFeatures($forcedLibsArray, $forcedLibsArray, $missing);
175                }
176                foreach ($gadget->getJsLibraries() as $library) {
177                        $type = $library->getType();
178                        if ($type == 'URL') {
179                                // TODO: This case needs to be handled more gracefully by the js
180                                // servlet. We should probably inline external JS as well.
181                                $externJs .= sprintf($externFmt, $library->getContent()) . "\n";
182                                // else check if there are no forcedLibs, or if it wasn't included in their dep chain
183                        } elseif (empty($forcedLibs) || ! in_array($library->getFeatureName(), $forcedLibsArray)) {
184                                $content .= $library->getContent();
185                        }
186                        // otherwise it was already included by config.forceJsLibs.
187                }
188                $content .= $this->appendJsConfig($context, $gadget, ! empty($forcedLibs)) . $this->appendMessages($gadget) . $this->appendPreloads($gadget, $context) . "</script>";
189                if (strlen($externJs) > 0) {
190                        $content .= $externJs;
191                }
192                $gadgetExceptions = array();
193                $rewriter = new ContentRewriter();
194                if ($rewriter->rewriteGadgetView($gadget, $view)) {
195                        $content .= $gadget->getSubstitutions()->substitute($view->getRewrittenContent());
196                } else {
197                        $content .= $gadget->getSubstitutions()->substitute($view->getContent());
198                }
199                if (empty($content)) {
200                        // Unknown view
201                        $gadgetExceptions[] = "View: '" . $context->getView() . "' invalid for gadget: " . $gadget->getId()->getKey();
202                }
203                if (count($gadgetExceptions)) {
204                        throw new GadgetException(print_r($gadgetExceptions, true));
205                }
206                $content .= "\n<script>gadgets.util.runOnLoadHandlers();</script></body>\n</html>";
207                echo $content;
208                }
209
210        /**
211         * Output's a URL content type gadget, it adds libs=<list:of:js:libraries>.js and user preferences
212         * to the href url, and redirects the browser to it
213         *
214         * @param Gadget $gadget
215         */
216        private function outputUrlGadget($gadget, $context, $view)
217        {
218                // Preserve existing query string parameters.
219                $redirURI = $view->getHref();
220                $queryStr = strpos($redirURI, '?') !== false ? substr($redirURI, strpos($redirURI, '?')) : '';
221                $query = $queryStr;
222                // TODO: userprefs on the fragment rather than query string
223                $query .= $this->getPrefsQueryString($gadget->getUserPrefValues());
224                $libs = array();
225                $forcedLibs = Config::get('focedJsLibs');
226                if ($forcedLibs == null) {
227                        $reqs = $gadget->getRequires();
228                        foreach ($reqs as $key => $val) {
229                                $libs[] = $key;
230                        }
231                } else {
232                        $libs = explode(':', $forcedLibs);
233                }
234                $query .= $this->appendLibsToQuery($libs, $gadget);
235                // code bugs out with me because of the invalid url syntax since we dont have a URI class to fix it for us
236                // this works around that
237                if (substr($query, 0, 1) == '&') {
238                        $query = '?' . substr($query, 1);
239                }
240                $redirURI .= $query;
241                header('Location: ' . $redirURI);
242                die();
243        }
244
245        /**
246         * Returns the requested libs (from getjsUrl) with the libs_param_name prepended
247         * ie: in libs=core:caja:etc.js format
248         *
249         * @param string $libs the libraries
250         * @param Gadget $gadget
251         * @return string the libs=... string to append to the redirection url
252         */
253        private function appendLibsToQuery($libs, $gadget)
254        {
255                $ret = "&";
256                $ret .= Config::get('libs_param_name');
257                $ret .= "=";
258                $ret .= $this->getJsUrl($libs, $gadget);
259                return $ret;
260        }
261
262        /**
263         * Returns the user preferences in &up_<name>=<val> format
264         *
265         * @param array $libs array of features this gadget requires
266         * @param Gadget $gadget
267         * @return string the up_<name>=<val> string to use in the redirection url
268         */
269        private function getPrefsQueryString($prefVals)
270        {
271                $ret = '';
272                foreach ($prefVals->getPrefs() as $key => $val) {
273                        $ret .= '&';
274                        $ret .= Config::get('userpref_param_prefix');
275                        $ret .= urlencode($key);
276                        $ret .= '=';
277                        $ret .= urlencode($val);
278                }
279                return $ret;
280        }
281
282        /**
283         * generates the library string (core:caja:etc.js) including a checksum of all the
284         * javascript content (?v=<md5 of js>) for cache busting
285         *
286         * @param string $libs
287         * @param Gadget $gadget
288         * @return string the list of libraries in core:caja:etc.js?v=checksum> format
289         */
290        private function getJsUrl($libs, $gadget)
291        {
292                $buf = '';
293                if (! is_array($libs) || ! count($libs)) {
294                        $buf = 'core';
295                } else {
296                        $firstDone = false;
297                        foreach ($libs as $lib) {
298                                if ($firstDone) {
299                                        $buf .= ':';
300                                } else {
301                                        $firstDone = true;
302                                }
303                                $buf .= $lib;
304                        }
305                }
306                $cache = $this->context->getCache();
307                if (($md5 = $cache->get(md5('getJsUrlMD5'))) === false) {
308                        $registry = $this->context->getRegistry();
309                        $features = $registry->getAllFeatures();
310                        // Build a version string from the md5() checksum of all included javascript
311                        // to ensure the client always has the right version
312                        $inlineJs = '';
313                        foreach ($features as $feature) {
314                                $library = $feature->getFeature();
315                                $libs = $library->getLibraries($this->context->getRenderingContext());
316                                foreach ($libs as $lib) {
317                                        $inlineJs .= $lib->getContent();
318                                }
319                        }
320                        $md5 = md5($inlineJs);
321                        $cache->set(md5('getJsUrlMD5'), $md5);
322                }
323                $buf .= ".js?v=" . $md5;
324                return $buf;
325        }
326
327        private function appendJsConfig($context, $gadget, $hasForcedLibs)
328        {
329                $container = $context->getContainer();
330                $containerConfig = $context->getContainerConfig();
331                //TODO some day we should parse the forcedLibs too, and include their config selectivly as well
332                // for now we just include everything if forced libs is set.
333                if ($hasForcedLibs) {
334                        $gadgetConfig = $containerConfig->getConfig($container, 'gadgets.features');
335                } else {
336                        $gadgetConfig = array();
337                        $featureConfig = $containerConfig->getConfig($container, 'gadgets.features');
338                        foreach ($gadget->getJsLibraries() as $library) {
339                                $feature = $library->getFeatureName();
340                                if (! isset($gadgetConfig[$feature]) && ! empty($featureConfig[$feature])) {
341                                        $gadgetConfig[$feature] = $featureConfig[$feature];
342                                }
343                        }
344                }
345                return "gadgets.config.init(" . json_encode($gadgetConfig) . ");\n";
346        }
347
348        private function appendMessages($gadget)
349        {
350                $msgs = '';
351                if ($gadget->getMessageBundle()) {
352                        $bundle = $gadget->getMessageBundle();
353                        $msgs = json_encode($bundle->getMessages());
354                }
355                return "gadgets.Prefs.setMessages_($msgs);\n";
356        }
357
358        /**
359         * Appends data from <Preload> elements to make them available to
360         * gadgets.io.
361         *
362         * @param gadget
363         */
364        private function appendPreloads(Gadget $gadget, GadgetContext $context)
365        {
366                $resp = Array();
367                $gadgetSigner = Config::get('security_token_signer');
368                $gadgetSigner = new $gadgetSigner();
369                $token = '';
370                try {
371                        $token = $context->extractAndValidateToken($gadgetSigner);
372                } catch (Exception $e) {
373                        $token = '';
374                        // no token given, safe to ignore
375                }
376                $unsignedRequests = $unsignedContexts = Array();
377                $signedRequests = Array();
378                foreach ($gadget->getPreloads() as $preload) {
379                        try {
380                                if (($preload->getAuth() == Auth::$NONE || $token != null) && (count($preload->getViews()) == 0 || in_array($context->getView(), $preload->getViews()))) {
381                                        $request = new RemoteContentRequest($preload->getHref());
382                                        $request->createRemoteContentRequestWithUri($preload->getHref());
383                                        $request->getOptions()->ownerSigned = $preload->isSignOwner();
384                                        $request->getOptions()->viewerSigned = $preload->isSignViewer();
385                                        switch (strtoupper(trim($preload->getAuth()))) {
386                                                case "NONE":
387                                                        //                                              Unify all unsigned requests to one single multi request
388                                                        $unsignedRequests[] = $request;
389                                                        $unsignedContexts[] = $context;
390                                                        break;
391                                                case "SIGNED":
392                                                        //                                              Unify all signed requests to one single multi request
393                                                        $signingFetcherFactory = new SigningFetcherFactory(Config::get("private_key_file"));
394                                                        $fetcher = $signingFetcherFactory->getSigningFetcher(new BasicRemoteContentFetcher(), $token);
395                                                        $req = $fetcher->signRequest($preload->getHref(), $request->getMethod());
396                                                        $req->setNotSignedUri($preload->getHref());
397                                                        $signedRequests[] = $req;
398                                                        break;
399                                                default:
400                                                        @ob_end_clean();
401                                                        header("HTTP/1.0 500 Internal Server Error", true);
402                                                        echo "<html><body><h1>" . "500 - Internal Server Error" . "</h1></body></html>";
403                                                        die();
404                                        }
405                                }
406                        } catch (Exception $e) {
407                                throw new Exception($e);
408                        }
409                }
410                if (count($unsignedRequests)) {
411                        try {
412                                $brc = new BasicRemoteContent();
413                                $responses = $brc->multiFetch($unsignedRequests, $unsignedContexts);
414                                foreach ($responses as $response) {
415                                        $resp[$response->getUrl()] = array(
416                                                        'body' => $response->getResponseContent(), 
417                                                        'rc' => $response->getHttpCode());
418                                }
419                        } catch (Exception $e) {
420                                throw new Exception($e);
421                        }
422                }
423                if (count($signedRequests)) {
424                        try {
425                                $fetcher = $signingFetcherFactory->getSigningFetcher(new BasicRemoteContentFetcher(), $token);
426                                $responses = $fetcher->multiFetchRequest($signedRequests);
427                                foreach ($responses as $response) {
428                                        $resp[$response->getNotSignedUrl()] = array(
429                                                        'body' => $response->getResponseContent(), 
430                                                        'rc' => $response->getHttpCode());
431                                }
432                        } catch (Exception $e) {
433                                throw new Exception($e);
434                        }
435                }               
436                $resp = count($resp) ? json_encode($resp) : "{}";
437                return "gadgets.io.preloaded_ = " . $resp . ";\n";
438        }
439}
Note: See TracBrowser for help on using the repository browser.