### Eclipse Workspace Patch 1.0 #P xwiki-platform-web Index: standard/pom.xml =================================================================== --- standard/pom.xml (revision 22346) +++ standard/pom.xml (working copy) @@ -62,6 +62,12 @@ xwiki-rest ${platform.core.version} + + + org.xwiki.platform + xwiki-social-opensocial + 1.0-SNAPSHOT + @@ -253,6 +259,9 @@ + **/containers/default/*.js + **/shindig.properties + **/opensocial/*.js **/*-min.js **/*-debug.js **/langs/*.js Index: standard/src/main/webapp/templates/addfriend.vm =================================================================== --- standard/src/main/webapp/templates/addfriend.vm (revision 0) +++ standard/src/main/webapp/templates/addfriend.vm (revision 0) @@ -0,0 +1,28 @@ +$response.setContentType("application/x-json") +## verify if the current user has the necessary rights to add a friend +## if no necessary rights, return 5 as error code +#if(!$hasAdmin && !$xwiki.hasAccessLevel("edit", $context.user, $doc.fullName)) + 5 +#else +#set($fullname = $request.get("name")) +## verify is the user is self +## is so, return 1 as error code +#if($fullname == $doc.fullName) + 1 +#else + ## verify if the user is already a friend + #set($exists = "$!doc.getObject('XWiki.FriendClass', 'friendName', $fullname)") + #if($exists != "") + ## verify if the user is already a friend + ## if so, return 2 as error code + 2 + #else + ## return 0, as success code + 0 + #set($obj = $doc.newObject("XWiki.FriendClass")) + $obj.set("friendName", $fullname) + #set($discard = $doc.save()) + #end +#end +#end + Index: standard/src/main/webapp/resources/js/xwiki/opensocial/util.js =================================================================== --- standard/src/main/webapp/resources/js/xwiki/opensocial/util.js (revision 0) +++ standard/src/main/webapp/resources/js/xwiki/opensocial/util.js (revision 0) @@ -0,0 +1,29 @@ +/** + * 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. + */ + +/** + * @fileoverview Utility functions for the Open Gadget Container + */ + +Function.prototype.inherits = function(parentCtor) { + function tempCtor() {}; + tempCtor.prototype = parentCtor.prototype; + this.superClass_ = parentCtor.prototype; + this.prototype = new tempCtor(); + this.prototype.constructor = this; +}; \ No newline at end of file Index: standard/src/main/webapp/templates/deletefriend.vm =================================================================== --- standard/src/main/webapp/templates/deletefriend.vm (revision 0) +++ standard/src/main/webapp/templates/deletefriend.vm (revision 0) @@ -0,0 +1,30 @@ +## verify if the current user has the necessary rights to add a friend +## if no necessary rights, return error +#set($hasRights = !$hasAdmin && !$xwiki.hasAccessLevel("edit", $context.user, $doc.fullName)) +#if($hasRights) + #set($rspMsg = "not enough rights") + #set($redirectUrl = "?xpart=error") +#else + #set($fullname = $request.get("name")) + #set($obj = $doc.getObject("XWiki.FriendClass", "friendName", $fullname)) + ## verify if document really has such an object + ## if not, return error + #if(!$obj) + #set($rspMsg = "no such object") + #set($redirectUrl = "?xpart=error") + #else + ## finally remove the object + ## return ok, as success code or redirect + #set($discard = $doc.removeObject($obj)) + #set($discard = $doc.save()) + #set($rspMsg = "ok") + #set($redirectUrl = "") + #end +#end +#if($request.ajax) + $rspMsg +#elseif($request.xredirect) + $response.sendRedirect("$request.xredirect$redirectUrl") +#else + $response.sendRedirect("$doc.getURL('view')$redirectUrl") +#end Index: standard/src/main/webapp/resources/js/xwiki/opensocial/cookies.js =================================================================== --- standard/src/main/webapp/resources/js/xwiki/opensocial/cookies.js (revision 0) +++ standard/src/main/webapp/resources/js/xwiki/opensocial/cookies.js (revision 0) @@ -0,0 +1,272 @@ +/** + * 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. + */ + +/** + * @fileoverview Functions for setting, getting and deleting cookies + */ + +/** + * Namespace for cookie functions + */ + +// TODO: find the official solution for a cookies library +var shindig = shindig || {}; +shindig.cookies = shindig.cookies || {}; + + +shindig.cookies.JsType_ = { + UNDEFINED: 'undefined' +}; + +shindig.cookies.isDef = function(val) { + return typeof val != shindig.cookies.JsType_.UNDEFINED; +}; + + +/** + * Sets a cookie. + * The max_age can be -1 to set a session cookie. To remove and expire cookies, + * use remove() instead. + * + * @param {string} name The cookie name. + * @param {string} value The cookie value. + * @param {number} opt_maxAge The max age in seconds (from now). Use -1 to set + * a session cookie. If not provided, the default is + * -1 (i.e. set a session cookie). + * @param {string} opt_path The path of the cookie, or null to not specify a + * path attribute (browser will use the full request + * path). If not provided, the default is '/' (i.e. + * path=/). + * @param {string} opt_domain The domain of the cookie, or null to not specify + * a domain attribute (browser will use the full + * request host name). If not provided, the default + * is null (i.e. let browser use full request host + * name). + */ +shindig.cookies.set = function(name, value, opt_maxAge, opt_path, opt_domain) { + // we do not allow '=' or ';' in the name + if (/;=/g.test(name)) { + throw new Error('Invalid cookie name "' + name + '"'); + } + // we do not allow ';' in value + if (/;/g.test(value)) { + throw new Error('Invalid cookie value "' + value + '"'); + } + + if (!shindig.cookies.isDef(opt_maxAge)) { + opt_maxAge = -1; + } + + var domainStr = opt_domain ? ';domain=' + opt_domain : ''; + var pathStr = opt_path ? ';path=' + opt_path : ''; + + var expiresStr; + + // Case 1: Set a session cookie. + if (opt_maxAge < 0) { + expiresStr = ''; + + // Case 2: Expire the cookie. + // Note: We don't tell people about this option in the function doc because + // we prefer people to use ExpireCookie() to expire cookies. + } else if (opt_maxAge === 0) { + // Note: Don't use Jan 1, 1970 for date because NS 4.76 will try to convert + // it to local time, and if the local time is before Jan 1, 1970, then the + // browser will ignore the Expires attribute altogether. + var pastDate = new Date(1970, 1 /*Feb*/, 1); // Feb 1, 1970 + expiresStr = ';expires=' + pastDate.toUTCString(); + + // Case 3: Set a persistent cookie. + } else { + var futureDate = new Date((new Date).getTime() + opt_maxAge * 1000); + expiresStr = ';expires=' + futureDate.toUTCString(); + } + + document.cookie = name + '=' + value + domainStr + pathStr + expiresStr; +}; + + +/** + * Returns the value for the first cookie with the given name + * @param {string} name The name of the cookie to get + * @param {string} opt_default If not found this is returned instead. + * @return {string|undefined} The value of the cookie. If no cookie is set this + * returns opt_default or undefined if opt_default is + * not provided. + */ +shindig.cookies.get = function(name, opt_default) { + var nameEq = name + "="; + var cookie = String(document.cookie); + for (var pos = -1; (pos = cookie.indexOf(nameEq, pos + 1)) >= 0;) { + var i = pos; + // walk back along string skipping whitespace and looking for a ; before + // the name to make sure that we don't match cookies whose name contains + // the given name as a suffix. + while (--i >= 0) { + var ch = cookie.charAt(i); + if (ch == ';') { + i = -1; // indicate success + break; + } + } + if (i == -1) { // first cookie in the string or we found a ; + var end = cookie.indexOf(';', pos); + if (end < 0) { + end = cookie.length; + } + return cookie.substring(pos + nameEq.length, end); + } + } + return opt_default; +}; + + +/** + * Removes and expires a cookie. + * + * @param {string} name The cookie name. + * @param {string} opt_path The path of the cookie, or null to expire a cookie + * set at the full request path. If not provided, the + * default is '/' (i.e. path=/). + * @param {string} opt_domain The domain of the cookie, or null to expire a + * cookie set at the full request host name. If not + * provided, the default is null (i.e. cookie at + * full request host name). + */ +shindig.cookies.remove = function(name, opt_path, opt_domain) { + var rv = shindig.cookies.containsKey(name); + shindig.cookies.set(name, '', 0, opt_path, opt_domain); + return rv; +}; + + +/** + * Gets the names and values for all the cookies + * @private + * @return {Object} An object with keys and values + */ +shindig.cookies.getKeyValues_ = function() { + var cookie = String(document.cookie); + var parts = cookie.split(/\s*;\s*/); + var keys = [], values = [], index, part; + for (var i = 0; part = parts[i]; i++) { + index = part.indexOf('='); + + if (index == -1) { // empty name + keys.push(''); + values.push(part); + } else { + keys.push(part.substring(0, index)); + values.push(part.substring(index + 1)); + } + } + return {keys: keys, values: values}; +}; + + +/** + * Gets the names for all the cookies + * @return {Array} An array with the names of the cookies + */ +shindig.cookies.getKeys = function() { + return shindig.cookies.getKeyValues_().keys; +}; + + +/** + * Gets the values for all the cookies + * @return {Array} An array with the values of the cookies + */ +shindig.cookies.getValues = function() { + return shindig.cookies.getKeyValues_().values; +}; + + +/** + * Whether there are any cookies for this document + * @return {boolean} + */ +shindig.cookies.isEmpty = function() { + return document.cookie === ''; +}; + + +/** + * Returns the number of cookies for this document + * @return {number} + */ +shindig.cookies.getCount = function() { + var cookie = String(document.cookie); + if (cookie === '') { + return 0; + } + var parts = cookie.split(/\s*;\s*/); + return parts.length; +}; + + +/** + * Returns whether there is a cookie with the given name + * @param {string} key The name of the cookie to test for + * @return {boolean} + */ +shindig.cookies.containsKey = function(key) { + var sentinel = {}; + // if get does not find the key it returns the default value. We therefore + // compare the result with an object to ensure we do not get any false + // positives. + return shindig.cookies.get(key, sentinel) !== sentinel; +}; + + +/** + * Returns whether there is a cookie with the given value. (This is an O(n) + * operation.) + * @param {string} value The value to check for + * @return {boolean} + */ +shindig.cookies.containsValue = function(value) { + // this O(n) in any case so lets do the trivial thing. + var values = shindig.cookies.getKeyValues_().values; + for (var i = 0; i < values.length; i++) { + if (values[i] == value) { + return true; + } + } + return false; +}; + + +/** + * Removes all cookies for this document + */ +shindig.cookies.clear = function() { + var keys = shindig.cookies.getKeyValues_().keys; + for (var i = keys.length - 1; i >= 0; i--) { + shindig.cookies.remove(keys[i]); + } +}; + +/** + * Static constant for the size of cookies. Per the spec, there's a 4K limit + * to the size of a cookie. To make sure users can't break this limit, we + * should truncate long cookies at 3950 bytes, to be extra careful with dumb + * browsers/proxies that interpret 4K as 4000 rather than 4096 + * @type number + */ +shindig.cookies.MAX_COOKIE_LENGTH = 3950; Index: standard/src/main/webapp/WEB-INF/classes/shindig.properties =================================================================== --- standard/src/main/webapp/WEB-INF/classes/shindig.properties (revision 0) +++ standard/src/main/webapp/WEB-INF/classes/shindig.properties (revision 0) @@ -0,0 +1,102 @@ +# 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. + +# Location of feature manifests (comma separated) +shindig.features.default=res://features/features.txt + +# Location of container configurations (comma separated) +shindig.containers.default=res://containers/default/container.js + +# A file containing blacklisted gadgets. +shindig.blacklist.file= + +### Inbound OAuth support +# The URL base to use for full OAuth support (three-legged) +shindig.oauth.base-url=/xwiki/oauth/ +shindig.oauth.authorize-action=/xwiki/WEB-INF/authorize.jsp +shindig.oauth.legacy-body-signing=true +shindig.oauth.enable-oauth-1.0=true +shindig.oauth.enable-signed-callbacks=true + +### Outbound OAuth support +shindig.signing.state-key= +shindig.signing.key-name= +shindig.signing.key-file= +shindig.signing.global-callback-url=http://localhost:8080/xwiki/gadgets/oauthcallback +shindig.signing.enable-signed-callbacks=true + +# If enabled here, configuration values can be found in container configuration files. +shindig.locked-domain.enabled=false + +# TODO: This needs to be moved to container configuration. +shindig.content-rewrite.include-urls=.* +shindig.content-rewrite.exclude-urls= +shindig.content-rewrite.include-tags=link,script,embed,img,style +shindig.content-rewrite.expires=86400 +shindig.content-rewrite.proxy-url=/xwiki/gadgets/proxy?url= +shindig.content-rewrite.concat-url=/xwiki/gadgets/concat? + +# +# Default set of forced libs to allow for better caching +# +# NOTE: setting this causes the EndToEnd test to fail the opensocial-templates test +#shindig.gadget-rewrite.default-forced-libs=core:core.io +shindig.gadget-rewrite.default-forced-libs= + +# Configuration for image rewriter +shindig.image-rewrite.max-inmem-bytes = 1048576 +shindig.image-rewrite.max-palette-size = 256 +shindig.image-rewrite.allow-jpeg-conversion = true +shindig.image-rewrite.jpeg-compression = 0.75 +shindig.image-rewrite.min-threshold-bytes = 200 + +# Configuration for template rewriter +shindig.template-rewrite.extension-tag-namespace=http://ns.opensocial.org/2009/extensions + +# These values provide default TTLs for HTTP responses that don't use caching headers. +shindig.cache.http.defaultTtl=3600000 +shindig.cache.http.negativeCacheTtl=60000 + +# A default refresh interval for XML files, since there is no natural way for developers to +# specify this value, and most HTTP responses don't include good cache control headers. +shindig.cache.xml.refreshInterval=300000 + +# Add entries in the form shindig.cache.lru..capacity to specify capacities for different +# caches when using the LruCacheProvider. +# It is highly recommended that the EhCache implementation be used instead of the LRU cache. +shindig.cache.lru.default.capacity=1000 +shindig.cache.lru.expressions.capacity=1000 +shindig.cache.lru.gadgetSpecs.capacity=1000 +shindig.cache.lru.messageBundles.capacity=1000 +shindig.cache.lru.httpResponses.capacity=10000 + +# The location of the EhCache configuration file. +shindig.cache.ehcache.config=res://org/apache/shindig/common/cache/ehcache/ehcacheConfig.xml + +# True to enable JMX integration with cache stats +shindig.cache.ehcache.jmx.enabled=true + +# true to enable JMX stats. +shindig.cache.ehcache.jmx.stats=true + +# true to skip expensive encoding detection. +# if true, will only attempt to validate utf-8. Assumes all other encodings are ISO-8859-1. +shindig.http.fast-encoding-detection=true + +# true to force strict content type checking for requests made to API endpoints. +# E.g. require application/json for JSON-RPC +shindig.api.disallow-unknown-content-types=true Index: standard/src/main/webapp/resources/js/xwiki/opensocial/gadgets.js =================================================================== --- standard/src/main/webapp/resources/js/xwiki/opensocial/gadgets.js (revision 0) +++ standard/src/main/webapp/resources/js/xwiki/opensocial/gadgets.js (revision 0) @@ -0,0 +1,804 @@ +/** + * 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. + */ + +/** + * @fileoverview Open Gadget Container + */ + +var gadgets = gadgets || {}; + +gadgets.error = {}; +gadgets.error.SUBCLASS_RESPONSIBILITY = 'subclass responsibility'; +gadgets.error.TO_BE_DONE = 'to be done'; + +gadgets.log = function(message) { + if (window.console && console.log) { + console.log(message); + } else { + var logEntry = document.createElement('div'); + logEntry.className = 'gadgets-log-entry'; + logEntry.innerHTML = message; + document.body.appendChild(logEntry); + } +}; + +/** + * Calls an array of asynchronous functions and calls the continuation + * function when all are done. + * @param {Array} functions Array of asynchronous functions, each taking + * one argument that is the continuation function that handles the result + * That is, each function is something like the following: + * function(continuation) { + * // compute result asynchronously + * continuation(result); + * } + * @param {Function} continuation Function to call when all results are in. It + * is pass an array of all results of all functions + * @param {Object} opt_this Optional object used as "this" when calling each + * function + */ +gadgets.callAsyncAndJoin = function(functions, continuation, opt_this) { + var pending = functions.length; + var results = []; + for (var i = 0; i < functions.length; i++) { + // we need a wrapper here because i changes and we need one index + // variable per closure + var wrapper = function(index) { + functions[index].call(opt_this, function(result) { + results[index] = result; + if (--pending === 0) { + continuation(results); + } + }); + }; + wrapper(i); + } +}; + + +// ---------- +// Extensible + +gadgets.Extensible = function() { +}; + +/** + * Sets the dependencies. + * @param {Object} dependencies Object whose properties are set on this + * container as dependencies + */ +gadgets.Extensible.prototype.setDependencies = function(dependencies) { + for (var p in dependencies) { + this[p] = dependencies[p]; + } +}; + +/** + * Returns a dependency given its name. + * @param {String} name Name of dependency + * @return {Object} Dependency with that name or undefined if not found + */ +gadgets.Extensible.prototype.getDependencies = function(name) { + return this[name]; +}; + + + +// ------------- +// UserPrefStore + +/** + * User preference store interface. + * @constructor + */ +gadgets.UserPrefStore = function() { +}; + +/** + * Gets all user preferences of a gadget. + * @param {Object} gadget Gadget object + * @return {Object} All user preference of given gadget + */ +gadgets.UserPrefStore.prototype.getPrefs = function(gadget) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +/** + * Saves user preferences of a gadget in the store. + * @param {Object} gadget Gadget object + * @param {Object} prefs User preferences + */ +gadgets.UserPrefStore.prototype.savePrefs = function(gadget) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + + +// ------------- +// DefaultUserPrefStore + +/** + * User preference store implementation. + * TODO: Turn this into a real implementation that is production safe + * @constructor + */ +gadgets.DefaultUserPrefStore = function() { + gadgets.UserPrefStore.call(this); +}; +gadgets.DefaultUserPrefStore.inherits(gadgets.UserPrefStore); + +gadgets.DefaultUserPrefStore.prototype.getPrefs = function(gadget) { }; + +gadgets.DefaultUserPrefStore.prototype.savePrefs = function(gadget) { }; + + +// ------------- +// GadgetService + +/** + * Interface of service provided to gadgets for resizing gadgets, + * setting title, etc. + * @constructor + */ +gadgets.GadgetService = function() { +}; + +gadgets.GadgetService.prototype.setHeight = function(elementId, height) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +gadgets.GadgetService.prototype.setTitle = function(gadget, title) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +gadgets.GadgetService.prototype.setUserPref = function(id) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + + +// ---------------- +// IfrGadgetService + +/** + * Base implementation of GadgetService. + * @constructor + */ +gadgets.IfrGadgetService = function() { + gadgets.GadgetService.call(this); + gadgets.rpc.register('resize_iframe', this.setHeight); + gadgets.rpc.register('set_pref', this.setUserPref); + gadgets.rpc.register('set_title', this.setTitle); + gadgets.rpc.register('requestNavigateTo', this.requestNavigateTo); +}; + +gadgets.IfrGadgetService.inherits(gadgets.GadgetService); + +gadgets.IfrGadgetService.prototype.setHeight = function(height) { + if (height > gadgets.container.maxheight_) { + height = gadgets.container.maxheight_; + } + + var element = document.getElementById(this.f); + if (element) { + element.style.height = height + 'px'; + } +}; + +gadgets.IfrGadgetService.prototype.setTitle = function(title) { + var element = document.getElementById(this.f + '_title'); + if (element) { + element.innerHTML = title.replace(/&/g, '&').replace(/ 0) { + url += '&appParams=' + encodeURIComponent(paramStr); + } + } + + if (url && document.location.href.indexOf(url) == -1) { + document.location.href = url; + } +}; + +/** + * This is a silly implementation that will need to be overriden by almost all + * real containers. + * TODO: Find a better default for this function + * + * @param view The view name to get the url for + */ +gadgets.IfrGadgetService.prototype.getUrlForView = function( + view) { + if (view === 'canvas') { + return '/canvas'; + } else if (view === 'profile') { + return '/profile'; + } else { + return null; + } +}; + +gadgets.IfrGadgetService.prototype.getGadgetIdFromModuleId = function( + moduleId) { + // Quick hack to extract the gadget id from module id + return parseInt(moduleId.match(/_([0-9]+)$/)[1], 10); +}; + + +// ------------- +// LayoutManager + +/** + * Layout manager interface. + * @constructor + */ +gadgets.LayoutManager = function() { +}; + +/** + * Gets the HTML element that is the chrome of a gadget into which the content + * of the gadget can be rendered. + * @param {Object} gadget Gadget instance + * @return {Object} HTML element that is the chrome for the given gadget + */ +gadgets.LayoutManager.prototype.getGadgetChrome = function(gadget) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +// ------------------- +// StaticLayoutManager + +/** + * Static layout manager where gadget ids have a 1:1 mapping to chrome ids. + * @constructor + */ +gadgets.StaticLayoutManager = function() { + gadgets.LayoutManager.call(this); +}; + +gadgets.StaticLayoutManager.inherits(gadgets.LayoutManager); + +/** + * Sets chrome ids, whose indexes are gadget instance ids (starting from 0). + * @param {Array} gadgetChromeIds Gadget id to chrome id map + */ +gadgets.StaticLayoutManager.prototype.setGadgetChromeIds = + function(gadgetChromeIds) { + this.gadgetChromeIds_ = gadgetChromeIds; +}; + +gadgets.StaticLayoutManager.prototype.getGadgetChrome = function(gadget) { + var chromeId = this.gadgetChromeIds_[gadget.id]; + return chromeId ? document.getElementById(chromeId) : null; +}; + + +// ---------------------- +// FloatLeftLayoutManager + +/** + * FloatLeft layout manager where gadget ids have a 1:1 mapping to chrome ids. + * @constructor + * @param {String} layoutRootId Id of the element that is the parent of all + * gadgets. + */ +gadgets.FloatLeftLayoutManager = function(layoutRootId) { + gadgets.LayoutManager.call(this); + this.layoutRootId_ = layoutRootId; +}; + +gadgets.FloatLeftLayoutManager.inherits(gadgets.LayoutManager); + +gadgets.FloatLeftLayoutManager.prototype.getGadgetChrome = + function(gadget) { + var layoutRoot = document.getElementById(this.layoutRootId_); + if (layoutRoot) { + var chrome = document.createElement('div'); + chrome.className = 'gadgets-gadget-chrome'; + chrome.style.cssFloat = 'left'; + layoutRoot.appendChild(chrome); + return chrome; + } else { + return null; + } +}; + + +// ------ +// Gadget + +/** + * Creates a new instance of gadget. Optional parameters are set as instance + * variables. + * @constructor + * @param {Object} params Parameters to set on gadget. Common parameters: + * "specUrl": URL to gadget specification + * "private": Whether gadget spec is accessible only privately, which means + * browser can load it but not gadget server + * "spec": Gadget Specification in XML + * "viewParams": a javascript object containing attribute value pairs + * for this gadgets + * "secureToken": an encoded token that is passed on the URL hash + * "hashData": Query-string like data that will be added to the + * hash portion of the URL. + * "specVersion": a hash value used to add a v= param to allow for better caching + * "title": the default title to use for the title bar. + * "height": height of the gadget + * "width": width of the gadget + * "debug": send debug=1 to the gadget server, gets us uncompressed + * javascript + */ +gadgets.Gadget = function(params) { + this.userPrefs_ = {}; + + if (params) { + for (var name in params) if (params.hasOwnProperty(name)) { + this[name] = params[name]; + } + } + if (!this.secureToken) { + // Assume that the default security token implementation is + // in use on the server. + this.secureToken = 'XWiki.Alice:XWiki.Alice:appid:cont:url:0:default'; + } +}; + +gadgets.Gadget.prototype.getUserPrefs = function() { + return this.userPrefs_; +}; + +gadgets.Gadget.prototype.setUserPrefs = function(userPrefs) { + this.userPrefs_ = userPrefs; + gadgets.container.userPrefStore.savePrefs(this); +}; + +gadgets.Gadget.prototype.getUserPref = function(name) { + return this.userPrefs_[name]; +}; + +gadgets.Gadget.prototype.setUserPref = function(name, value) { + this.userPrefs_[name] = value; + gadgets.container.userPrefStore.savePrefs(this); +}; + +gadgets.Gadget.prototype.render = function(chrome) { + if (chrome) { + var gadget = this; + this.getContent(function(content) { + chrome.innerHTML = content; + window.frames[gadget.getIframeId()].location = gadget.getIframeUrl(); + }); + } +}; + +gadgets.Gadget.prototype.getContent = function(continuation) { + gadgets.callAsyncAndJoin([ + this.getTitleBarContent, this.getUserPrefsDialogContent, + this.getMainContent], function(results) { + continuation(results.join('')); + }, this); +}; + +/** + * Gets title bar content asynchronously or synchronously. + * @param {Function} continuation Function that handles title bar content as + * the one and only argument + */ +gadgets.Gadget.prototype.getTitleBarContent = function(continuation) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +/** + * Gets user preferences dialog content asynchronously or synchronously. + * @param {Function} continuation Function that handles user preferences + * content as the one and only argument + */ +gadgets.Gadget.prototype.getUserPrefsDialogContent = function(continuation) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +/** + * Gets gadget content asynchronously or synchronously. + * @param {Function} continuation Function that handles gadget content as + * the one and only argument + */ +gadgets.Gadget.prototype.getMainContent = function(continuation) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +/* + * Gets additional parameters to append to the iframe url + * Override this method if you need any custom params. + */ +gadgets.Gadget.prototype.getAdditionalParams = function() { + return ''; +}; + + +// --------- +// IfrGadget + +gadgets.IfrGadget = function(opt_params) { + gadgets.Gadget.call(this, opt_params); + this.serverBase_ = '../../../'; // default gadget server +}; + +gadgets.IfrGadget.inherits(gadgets.Gadget); + +gadgets.IfrGadget.prototype.GADGET_IFRAME_PREFIX_ = 'remote_iframe_'; + +gadgets.IfrGadget.prototype.CONTAINER = 'default'; + +gadgets.IfrGadget.prototype.cssClassGadget = 'gadgets-gadget'; +gadgets.IfrGadget.prototype.cssClassTitleBar = 'gadgets-gadget-title-bar'; +gadgets.IfrGadget.prototype.cssClassTitle = 'gadgets-gadget-title'; +gadgets.IfrGadget.prototype.cssClassTitleButtonBar = + 'gadgets-gadget-title-button-bar'; +gadgets.IfrGadget.prototype.cssClassGadgetUserPrefsDialog = + 'gadgets-gadget-user-prefs-dialog'; +gadgets.IfrGadget.prototype.cssClassGadgetUserPrefsDialogActionBar = + 'gadgets-gadget-user-prefs-dialog-action-bar'; +gadgets.IfrGadget.prototype.cssClassTitleButton = 'gadgets-gadget-title-button'; +gadgets.IfrGadget.prototype.cssClassGadgetContent = 'gadgets-gadget-content'; +gadgets.IfrGadget.prototype.rpcToken = (0x7FFFFFFF * Math.random()) | 0; +gadgets.IfrGadget.prototype.rpcRelay = 'files/container/rpc_relay.html'; + +gadgets.IfrGadget.prototype.getTitleBarContent = function(continuation) { + continuation('
' + (this.title ? this.title : 'Title') + ' | settings toggle
'); +}; + +gadgets.IfrGadget.prototype.getUserPrefsDialogContent = function(continuation) { + continuation('
'); +}; + +gadgets.IfrGadget.prototype.setServerBase = function(url) { + this.serverBase_ = url; +}; + +gadgets.IfrGadget.prototype.getServerBase = function() { + return this.serverBase_; +}; + +gadgets.IfrGadget.prototype.getMainContent = function(continuation) { + var iframeId = this.getIframeId(); + gadgets.rpc.setRelayUrl(iframeId, this.serverBase_ + this.rpcRelay); + gadgets.rpc.setAuthToken(iframeId, this.rpcToken); + continuation('
'); +}; + +gadgets.IfrGadget.prototype.getIframeId = function() { + return this.GADGET_IFRAME_PREFIX_ + this.id; +}; + +gadgets.IfrGadget.prototype.getUserPrefsDialogId = function() { + return this.getIframeId() + '_userPrefsDialog'; +}; + +gadgets.IfrGadget.prototype.getIframeUrl = function() { + return this.serverBase_ + 'gadgets/ifr?' + + 'container=' + this.CONTAINER + + '&mid=' + this.id + + '&nocache=' + gadgets.container.nocache_ + + '&country=' + gadgets.container.country_ + + '&lang=' + gadgets.container.language_ + + '&view=' + gadgets.container.view_ + + (this.specVersion ? '&v=' + this.specVersion : '') + + (gadgets.container.parentUrl_ ? '&parent=' + encodeURIComponent(gadgets.container.parentUrl_) : '') + + (this.debug ? '&debug=1' : '') + + this.getAdditionalParams() + + this.getUserPrefsParams() + + (this.secureToken ? '&st=' + this.secureToken : '') + + '&url=' + encodeURIComponent(this.specUrl) + + '#rpctoken=' + this.rpcToken + + (this.viewParams ? + '&view-params=' + encodeURIComponent(gadgets.json.stringify(this.viewParams)) : '') + + (this.hashData ? '&' + this.hashData : ''); +}; + +gadgets.IfrGadget.prototype.getUserPrefsParams = function() { + var params = ''; + if (this.getUserPrefs()) { + for(var name in this.getUserPrefs()) { + var value = this.getUserPref(name); + params += '&up_' + encodeURIComponent(name) + '=' + + encodeURIComponent(value); + } + } + return params; +}; + +gadgets.IfrGadget.prototype.handleToggle = function() { + var gadgetIframe = document.getElementById(this.getIframeId()); + if (gadgetIframe) { + var gadgetContent = gadgetIframe.parentNode; + var display = gadgetContent.style.display; + gadgetContent.style.display = display ? '' : 'none'; + } +}; + +gadgets.IfrGadget.prototype.handleOpenUserPrefsDialog = function() { + if (this.userPrefsDialogContentLoaded) { + this.showUserPrefsDialog(); + } else { + var gadget = this; + var igCallbackName = 'ig_callback_' + this.id; + window[igCallbackName] = function(userPrefsDialogContent) { + gadget.userPrefsDialogContentLoaded = true; + gadget.buildUserPrefsDialog(userPrefsDialogContent); + gadget.showUserPrefsDialog(); + }; + + var script = document.createElement('script'); + script.src = 'http://gmodules.com/ig/gadgetsettings?mid=' + this.id + + '&output=js' + this.getUserPrefsParams() + '&url=' + this.specUrl; + document.body.appendChild(script); + } +}; + +gadgets.IfrGadget.prototype.buildUserPrefsDialog = function(content) { + var userPrefsDialog = document.getElementById(this.getUserPrefsDialogId()); + userPrefsDialog.innerHTML = content + + '
'; + userPrefsDialog.childNodes[0].style.display = ''; +}; + +gadgets.IfrGadget.prototype.showUserPrefsDialog = function(opt_show) { + var userPrefsDialog = document.getElementById(this.getUserPrefsDialogId()); + userPrefsDialog.style.display = (opt_show || opt_show === undefined) + ? '' : 'none'; +}; + +gadgets.IfrGadget.prototype.hideUserPrefsDialog = function() { + this.showUserPrefsDialog(false); +}; + +gadgets.IfrGadget.prototype.handleSaveUserPrefs = function() { + this.hideUserPrefsDialog(); + + var prefs = {}; + var numFields = document.getElementById('m_' + this.id + + '_numfields').value; + for (var i = 0; i < numFields; i++) { + var input = document.getElementById('m_' + this.id + '_' + i); + if (input.type != 'hidden') { + var userPrefNamePrefix = 'm_' + this.id + '_up_'; + var userPrefName = input.name.substring(userPrefNamePrefix.length); + var userPrefValue = input.value; + prefs[userPrefName] = userPrefValue; + } + } + + this.setUserPrefs(prefs); + this.refresh(); +}; + +gadgets.IfrGadget.prototype.handleCancelUserPrefs = function() { + this.hideUserPrefsDialog(); +}; + +gadgets.IfrGadget.prototype.refresh = function() { + var iframeId = this.getIframeId(); + document.getElementById(iframeId).src = this.getIframeUrl(); +}; + + +// --------- +// Container + +/** + * Container interface. + * @constructor + */ +gadgets.Container = function() { + this.gadgets_ = {}; + this.parentUrl_ = 'http://' + document.location.host; + this.country_ = 'ALL'; + this.language_ = 'ALL'; + this.view_ = 'default'; + this.nocache_ = 1; + + // signed max int + this.maxheight_ = 0x7FFFFFFF; +}; + +gadgets.Container.inherits(gadgets.Extensible); + +/** + * Known dependencies: + * gadgetClass: constructor to create a new gadget instance + * userPrefStore: instance of a subclass of gadgets.UserPrefStore + * gadgetService: instance of a subclass of gadgets.GadgetService + * layoutManager: instance of a subclass of gadgets.LayoutManager + */ + +gadgets.Container.prototype.gadgetClass = gadgets.Gadget; + +gadgets.Container.prototype.userPrefStore = new gadgets.DefaultUserPrefStore(); + +gadgets.Container.prototype.gadgetService = new gadgets.GadgetService(); + +gadgets.Container.prototype.layoutManager = + new gadgets.StaticLayoutManager(); + +gadgets.Container.prototype.setParentUrl = function(url) { + this.parentUrl_ = url; +}; + +gadgets.Container.prototype.setCountry = function(country) { + this.country_ = country; +}; + +gadgets.Container.prototype.setNoCache = function(nocache) { + this.nocache_ = nocache; +}; + +gadgets.Container.prototype.setLanguage = function(language) { + this.language_ = language; +}; + +gadgets.Container.prototype.setView = function(view) { + this.view_ = view; +}; + +gadgets.Container.prototype.setMaxHeight = function(maxheight) { + this.maxheight_ = maxheight; +}; + +gadgets.Container.prototype.getGadgetKey_ = function(instanceId) { + return 'gadget_' + instanceId; +}; + +gadgets.Container.prototype.getGadget = function(instanceId) { + return this.gadgets_[this.getGadgetKey_(instanceId)]; +}; + +gadgets.Container.prototype.createGadget = function(opt_params) { + return new this.gadgetClass(opt_params); +}; + +gadgets.Container.prototype.addGadget = function(gadget) { + gadget.id = this.getNextGadgetInstanceId(); + gadget.setUserPrefs(this.userPrefStore.getPrefs(gadget)); + this.gadgets_[this.getGadgetKey_(gadget.id)] = gadget; +}; + +gadgets.Container.prototype.addGadgets = function(gadgets) { + for (var i = 0; i < gadgets.length; i++) { + this.addGadget(gadgets[i]); + } +}; + +/** + * Renders all gadgets in the container. + */ +gadgets.Container.prototype.renderGadgets = function() { + for (var key in this.gadgets_) { + this.renderGadget(this.gadgets_[key]); + } +}; + +/** + * Renders a gadget. Gadgets are rendered inside their chrome element. + * @param {Object} gadget Gadget object + */ +gadgets.Container.prototype.renderGadget = function(gadget) { + throw Error(gadgets.error.SUBCLASS_RESPONSIBILITY); +}; + +gadgets.Container.prototype.nextGadgetInstanceId_ = 0; + +gadgets.Container.prototype.getNextGadgetInstanceId = function() { + return this.nextGadgetInstanceId_++; +}; + +/** + * Refresh all the gadgets in the container. + */ +gadgets.Container.prototype.refreshGadgets = function() { + for (var key in this.gadgets_) { + this.gadgets_[key].refresh(); + } +}; + + +// ------------ +// IfrContainer + +/** + * Container that renders gadget using ifr. + * @constructor + */ +gadgets.IfrContainer = function() { + gadgets.Container.call(this); +}; + +gadgets.IfrContainer.inherits(gadgets.Container); + +gadgets.IfrContainer.prototype.gadgetClass = gadgets.IfrGadget; + +gadgets.IfrContainer.prototype.gadgetService = new gadgets.IfrGadgetService(); + +gadgets.IfrContainer.prototype.setParentUrl = function(url) { + if (!url.match(/^http[s]?:\/\//)) { + url = document.location.href.match(/^[^?#]+\//)[0] + url; + } + + this.parentUrl_ = url; +}; + +/** + * Renders a gadget using ifr. + * @param {Object} gadget Gadget object + */ +gadgets.IfrContainer.prototype.renderGadget = function(gadget) { + var chrome = this.layoutManager.getGadgetChrome(gadget); + gadget.render(chrome); +}; + +/** + * Default container. + */ +gadgets.container = new gadgets.IfrContainer(); Index: standard/src/main/webapp/WEB-INF/web.xml =================================================================== --- standard/src/main/webapp/WEB-INF/web.xml (revision 22419) +++ standard/src/main/webapp/WEB-INF/web.xml (working copy) @@ -15,6 +15,18 @@ + + + guice-modules + + org.apache.shindig.common.PropertiesModule: + org.apache.shindig.gadgets.DefaultGuiceModule: + org.apache.shindig.gadgets.oauth.OAuthModule: + org.apache.shindig.common.cache.ehcache.EhCacheModule: + org.xwiki.opensocial.social.XWSocialModule + + + + + authFilter + org.apache.shindig.auth.AuthenticationServletFilter + @@ -103,10 +121,41 @@ /* + + + authFilter + /social/* + + + + authFilter + /gadgets/ifr + + + + authFilter + /gadgets/makeRequest + + + + authFilter + /gadgets/api/rpc/* + + + + authFilter + /gadgets/api/rest/* + + org.xwiki.container.servlet.XWikiServletContextListener + + + + org.apache.shindig.common.servlet.GuiceServletContextListener + org.xwiki.container.servlet.SetThreadNameServletRequestListener @@ -192,6 +241,108 @@ + + + + xml-to-html + + org.apache.shindig.gadgets.servlet.GadgetRenderingServlet + + + + + + proxy + + org.apache.shindig.gadgets.servlet.ProxyServlet + + + + + + makeRequest + + org.apache.shindig.gadgets.servlet.MakeRequestServlet + + + + + concat + + org.apache.shindig.gadgets.servlet.ConcatProxyServlet + + + + + + oauthCallback + + org.apache.shindig.gadgets.servlet.OAuthCallbackServlet + + + + + + metadata + + org.apache.shindig.gadgets.servlet.RpcServlet + + + + + + js + org.apache.shindig.gadgets.servlet.JsServlet + + + + + socialRestapiServlet + + org.apache.shindig.protocol.DataServiceServlet + + + handlers + org.apache.shindig.social.handlers + + + + + + socialJsonRpcServlet + + org.apache.shindig.protocol.JsonRpcServlet + + + handlers + org.apache.shindig.social.handlers + + + + + + gadgetsJsonRpcServlet + + org.apache.shindig.protocol.JsonRpcServlet + + + handlers + org.apache.shindig.gadgets.handlers + + + + + + gadgetsRestapiServlet + + org.apache.shindig.protocol.DataServiceServlet + + + handlers + org.apache.shindig.gadgets.handlers + + + RestletServlet @@ -242,6 +393,62 @@ webdav /webdav/* + + + + js + /gadgets/js/* + + + + proxy + /gadgets/proxy/* + + + + makeRequest + /gadgets/makeRequest + + + + gadgetsJsonRpcServlet + /gadgets/api/rpc/* + + + + gadgetsRestapiServlet + /gadgets/api/rest/* + + + + concat + /gadgets/concat + + + + oauthCallback + /gadgets/oauthcallback + + + + xml-to-html + /gadgets/ifr + + + + metadata + /gadgets/metadata + + + + socialRestapiServlet + /social/rest/* + + + + socialJsonRpcServlet + /social/rpc/* +