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

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

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

added opOpenSocialPlugin

File size: 15.2 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                $externJs = "";
135                $externFmt = "<script src=\"%s\"></script>";
136                $forcedLibs = $context->getForcedJsLibs();
137                // allow the &libs=.. param to override our forced js libs configuration value
138                if (empty($forcedLibs)) {
139                        $forcedLibs = Config::get('focedJsLibs');
140                }
141                $this->setContentType("text/html; charset=UTF-8");
142                if ($context->getIgnoreCache()) {
143                        // no cache was requested, set non-caching-headers
144                        $this->setNoCache(true);
145                } elseif (isset($_GET['v'])) {
146                        // version was given, cache for a long long time (a year)
147                        $this->setCacheTime(365 * 24 * 60 * 60);
148                } else {
149                        // no version was given, cache for 5 minutes
150                        $this->setCacheTime(5 * 60);
151                }
152                // Was a privacy policy header configured? if so set it
153                if (Config::get('P3P') != '') {
154                        header("P3P: " . Config::get('P3P'));
155                }
156                if (! $view->getQuirks()) {
157                        echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n";
158                }
159                echo "<html><head><style type=\"text/css\">" . Config::get('gadget_css') . "</style></head><body>\n";
160                // Forced libs first.
161                if (! empty($forcedLibs)) {
162                        $libs = explode(':', $forcedLibs);
163                        echo sprintf($externFmt, Config::get('default_js_prefix') . $this->getJsUrl($libs, $gadget) . "&container=" . $context->getContainer()) . "\n";
164                }
165                echo "<script>\n";
166               
167                if (! empty($forcedLibs)) {
168                        // if some of the feature libraries are externalized (through a browser cachable <script src="/gadgets/js/opensocial-0.7:settitle.js">
169                        // type url), then we don't want to include dependencies twice, so find the complete features chain, so we can skip over those
170                        $forcedLibsArray = explode(':', $forcedLibs);
171                        $registry = $this->context->getRegistry();
172                        $missing = array();
173                        $registry->getIncludedFeatures($forcedLibsArray, $forcedLibsArray, $missing);
174                }
175                foreach ($gadget->getJsLibraries() as $library) {
176                        $type = $library->getType();
177                        if ($type == 'URL') {
178                                // TODO: This case needs to be handled more gracefully by the js
179                                // servlet. We should probably inline external JS as well.
180                                $externJs .= sprintf($externFmt, $library->getContent()) . "\n";
181                                // else check if there are no forcedLibs, or if it wasn't included in their dep chain
182                        } elseif (empty($forcedLibs) || ! in_array($library->getFeatureName(), $forcedLibsArray)) {
183                                echo $library->getContent();
184                        }
185                        // otherwise it was already included by config.forceJsLibs.
186                }
187                echo $this->appendJsConfig($context, $gadget, ! empty($forcedLibs)) . $this->appendMessages($gadget) . $this->appendPreloads($gadget, $context) . "</script>";
188                if (strlen($externJs) > 0) {
189                        echo $externJs;
190                }
191                $gadgetExceptions = array();
192                $rewriter = new ContentRewriter();
193                if ($rewriter->rewriteGadgetView($gadget, $view)) {
194                        $content = $gadget->getSubstitutions()->substitute($view->getRewrittenContent());
195                } else {
196                        $content = $gadget->getSubstitutions()->substitute($view->getContent());
197                }
198                if (empty($content)) {
199                        // Unknown view
200                        $gadgetExceptions[] = "View: '" . $context->getView() . "' invalid for gadget: " . $gadget->getId()->getKey();
201                }
202                if (count($gadgetExceptions)) {
203                        throw new GadgetException(print_r($gadgetExceptions, true));
204                }
205                echo $content . "\n<script>gadgets.util.runOnLoadHandlers();</script></body>\n</html>";
206        }
207
208        /**
209         * Output's a URL content type gadget, it adds libs=<list:of:js:libraries>.js and user preferences
210         * to the href url, and redirects the browser to it
211         *
212         * @param Gadget $gadget
213         */
214        private function outputUrlGadget($gadget, $context, $view)
215        {
216                // Preserve existing query string parameters.
217                $redirURI = $view->getHref();
218                $queryStr = strpos($redirURI, '?') !== false ? substr($redirURI, strpos($redirURI, '?')) : '';
219                $query = $queryStr;
220                // TODO: userprefs on the fragment rather than query string
221                $query .= $this->getPrefsQueryString($gadget->getUserPrefValues());
222                $libs = array();
223                $forcedLibs = Config::get('focedJsLibs');
224                if ($forcedLibs == null) {
225                        $reqs = $gadget->getRequires();
226                        foreach ($reqs as $key => $val) {
227                                $libs[] = $key;
228                        }
229                } else {
230                        $libs = explode(':', $forcedLibs);
231                }
232                $query .= $this->appendLibsToQuery($libs, $gadget);
233                // code bugs out with me because of the invalid url syntax since we dont have a URI class to fix it for us
234                // this works around that
235                if (substr($query, 0, 1) == '&') {
236                        $query = '?' . substr($query, 1);
237                }
238                $redirURI .= $query;
239                header('Location: ' . $redirURI);
240                die();
241        }
242
243        /**
244         * Returns the requested libs (from getjsUrl) with the libs_param_name prepended
245         * ie: in libs=core:caja:etc.js format
246         *
247         * @param string $libs the libraries
248         * @param Gadget $gadget
249         * @return string the libs=... string to append to the redirection url
250         */
251        private function appendLibsToQuery($libs, $gadget)
252        {
253                $ret = "&";
254                $ret .= Config::get('libs_param_name');
255                $ret .= "=";
256                $ret .= $this->getJsUrl($libs, $gadget);
257                return $ret;
258        }
259
260        /**
261         * Returns the user preferences in &up_<name>=<val> format
262         *
263         * @param array $libs array of features this gadget requires
264         * @param Gadget $gadget
265         * @return string the up_<name>=<val> string to use in the redirection url
266         */
267        private function getPrefsQueryString($prefVals)
268        {
269                $ret = '';
270                foreach ($prefVals->getPrefs() as $key => $val) {
271                        $ret .= '&';
272                        $ret .= Config::get('userpref_param_prefix');
273                        $ret .= urlencode($key);
274                        $ret .= '=';
275                        $ret .= urlencode($val);
276                }
277                return $ret;
278        }
279
280        /**
281         * generates the library string (core:caja:etc.js) including a checksum of all the
282         * javascript content (?v=<md5 of js>) for cache busting
283         *
284         * @param string $libs
285         * @param Gadget $gadget
286         * @return string the list of libraries in core:caja:etc.js?v=checksum> format
287         */
288        private function getJsUrl($libs, $gadget)
289        {
290                $buf = '';
291                if (! is_array($libs) || ! count($libs)) {
292                        $buf = 'core';
293                } else {
294                        $firstDone = false;
295                        foreach ($libs as $lib) {
296                                if ($firstDone) {
297                                        $buf .= ':';
298                                } else {
299                                        $firstDone = true;
300                                }
301                                $buf .= $lib;
302                        }
303                }
304                $cache = $this->context->getCache();
305                if (($md5 = $cache->get(md5('getJsUrlMD5'))) === false) {
306                        $registry = $this->context->getRegistry();
307                        $features = $registry->getAllFeatures();
308                        // Build a version string from the md5() checksum of all included javascript
309                        // to ensure the client always has the right version
310                        $inlineJs = '';
311                        foreach ($features as $feature) {
312                                $library = $feature->getFeature();
313                                $libs = $library->getLibraries($this->context->getRenderingContext());
314                                foreach ($libs as $lib) {
315                                        $inlineJs .= $lib->getContent();
316                                }
317                        }
318                        $md5 = md5($inlineJs);
319                        $cache->set(md5('getJsUrlMD5'), $md5);
320                }
321                $buf .= ".js?v=" . $md5;
322                return $buf;
323        }
324
325        private function appendJsConfig($context, $gadget, $hasForcedLibs)
326        {
327                $container = $context->getContainer();
328                $containerConfig = $context->getContainerConfig();
329                //TODO some day we should parse the forcedLibs too, and include their config selectivly as well
330                // for now we just include everything if forced libs is set.
331                if ($hasForcedLibs) {
332                        $gadgetConfig = $containerConfig->getConfig($container, 'gadgets.features');
333                } else {
334                        $gadgetConfig = array();
335                        $featureConfig = $containerConfig->getConfig($container, 'gadgets.features');
336                        foreach ($gadget->getJsLibraries() as $library) {
337                                $feature = $library->getFeatureName();
338                                if (! isset($gadgetConfig[$feature]) && ! empty($featureConfig[$feature])) {
339                                        $gadgetConfig[$feature] = $featureConfig[$feature];
340                                }
341                        }
342                }
343                return "gadgets.config.init(" . json_encode($gadgetConfig) . ");\n";
344        }
345
346        private function appendMessages($gadget)
347        {
348                $msgs = '';
349                if ($gadget->getMessageBundle()) {
350                        $bundle = $gadget->getMessageBundle();
351                        $msgs = json_encode($bundle->getMessages());
352                }
353                return "gadgets.Prefs.setMessages_($msgs);\n";
354        }
355
356        /**
357         * Appends data from <Preload> elements to make them available to
358         * gadgets.io.
359         *
360         * @param gadget
361         */
362        private function appendPreloads(Gadget $gadget, GadgetContext $context)
363        {
364                $resp = Array();
365                $gadgetSigner = Config::get('security_token_signer');
366                $gadgetSigner = new $gadgetSigner();
367                $token = '';
368                try {
369                        $token = $context->extractAndValidateToken($gadgetSigner);
370                } catch (Exception $e) {
371                        $token = '';
372                        // no token given, safe to ignore
373                }
374                $unsignedRequests = $unsignedContexts = Array();
375                $signedRequests = Array();
376                foreach ($gadget->getPreloads() as $preload) {
377                        try {
378                                if (($preload->getAuth() == Auth::$NONE || $token != null) && (count($preload->getViews()) == 0 || in_array($context->getView(), $preload->getViews()))) {
379                                        $request = new RemoteContentRequest($preload->getHref());
380                                        $request->createRemoteContentRequestWithUri($preload->getHref());
381                                        $request->getOptions()->ownerSigned = $preload->isSignOwner();
382                                        $request->getOptions()->viewerSigned = $preload->isSignViewer();
383                                        switch (strtoupper(trim($preload->getAuth()))) {
384                                                case "NONE":
385                                                        //                                              Unify all unsigned requests to one single multi request
386                                                        $unsignedRequests[] = $request;
387                                                        $unsignedContexts[] = $context;
388                                                        break;
389                                                case "SIGNED":
390                                                        //                                              Unify all signed requests to one single multi request
391                                                        $signingFetcherFactory = new SigningFetcherFactory(Config::get("private_key_file"));
392                                                        $fetcher = $signingFetcherFactory->getSigningFetcher(new BasicRemoteContentFetcher(), $token);
393                                                        $req = $fetcher->signRequest($preload->getHref(), $request->getMethod());
394                                                        $req->setNotSignedUri($preload->getHref());
395                                                        $signedRequests[] = $req;
396                                                        break;
397                                                default:
398                                                        @ob_end_clean();
399                                                        header("HTTP/1.0 500 Internal Server Error", true);
400                                                        echo "<html><body><h1>" . "500 - Internal Server Error" . "</h1></body></html>";
401                                                        die();
402                                        }
403                                }
404                        } catch (Exception $e) {
405                                throw new Exception($e);
406                        }
407                }
408                if (count($unsignedRequests)) {
409                        try {
410                                $brc = new BasicRemoteContent();
411                                $responses = $brc->multiFetch($unsignedRequests, $unsignedContexts);
412                                foreach ($responses as $response) {
413                                        $resp[$response->getUrl()] = array(
414                                                        'body' => $response->getResponseContent(), 
415                                                        'rc' => $response->getHttpCode());
416                                }
417                        } catch (Exception $e) {
418                                throw new Exception($e);
419                        }
420                }
421                if (count($signedRequests)) {
422                        try {
423                                $fetcher = $signingFetcherFactory->getSigningFetcher(new BasicRemoteContentFetcher(), $token);
424                                $responses = $fetcher->multiFetchRequest($signedRequests);
425                                foreach ($responses as $response) {
426                                        $resp[$response->getNotSignedUrl()] = array(
427                                                        'body' => $response->getResponseContent(), 
428                                                        'rc' => $response->getHttpCode());
429                                }
430                        } catch (Exception $e) {
431                                throw new Exception($e);
432                        }
433                }               
434                $resp = count($resp) ? json_encode($resp) : "{}";
435                return "gadgets.io.preloaded_ = " . $resp . ";\n";
436        }
437}
Note: See TracBrowser for help on using the repository browser.