diff options
18 files changed, 742 insertions, 26 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/etherpad/src/etherpad/control/static_control.js b/etherpad/src/etherpad/control/static_control.js index 5c087b6..d938b26 100644 --- a/etherpad/src/etherpad/control/static_control.js +++ b/etherpad/src/etherpad/control/static_control.js @@ -19,12 +19,25 @@ import("dispatch.{Dispatcher,PrefixMatcher,forward}"); import("etherpad.utils.*"); import("etherpad.globals.*"); +import("etherpad.admin.plugins"); function onRequest() { var staticBase = '/static'; var opts = {cache: isProduction()}; + var disp = new Dispatcher(); + + /* FIXME: Is there a more effective way to do this? */ + for (plugin in plugins.plugins) { + disp.addLocations([ + [PrefixMatcher('/static/js/plugins/'+plugin+'/'), faststatic.directoryServer('/plugins/' + plugin + '/static/js/', opts)], + [PrefixMatcher('/static/css/plugins/'+plugin+'/'), faststatic.directoryServer('/plugins/' + plugin + '/static/css/', opts)], + [PrefixMatcher('/static/swf/plugins/'+plugin+'/'), faststatic.directoryServer('/plugins/' + plugin + '/static/swf/', opts)], + [PrefixMatcher('/static/html/plugins/'+plugin+'/'), faststatic.directoryServer('/plugins/' + plugin + '/static/html/', opts)], + [PrefixMatcher('/static/zip/plugins/'+plugin+'/'), faststatic.directoryServer('/plugins/' + plugin + '/static/zip/', opts)]]); + } + var serveFavicon = faststatic.singleFileServer(staticBase + '/favicon.ico', opts); var serveCrossDomain = faststatic.singleFileServer(staticBase + '/crossdomain.xml', opts); var serveStaticDir = faststatic.directoryServer(staticBase, opts); @@ -35,8 +48,6 @@ function onRequest() { var serveHtml = faststatic.directoryServer(staticBase+'/html/', opts); var serveZip = faststatic.directoryServer(staticBase+'/zip/', opts); - var disp = new Dispatcher(); - disp.addLocations([ ['/favicon.ico', serveFavicon], ['/robots.txt', serveRobotsTxt], diff --git a/etherpad/src/etherpad/utils.js b/etherpad/src/etherpad/utils.js index da9972f..e60c08a 100644 --- a/etherpad/src/etherpad/utils.js +++ b/etherpad/src/etherpad/utils.js @@ -34,6 +34,8 @@ import("etherpad.pro.pro_utils"); import("etherpad.pro.pro_config"); import("etherpad.pro.pro_accounts"); import("etherpad.pro.pro_accounts.getSessionProAccount"); +import("etherpad.log"); +import("etherpad.admin.plugins"); jimport("java.lang.System.out.print"); jimport("java.lang.System.out.println"); @@ -55,11 +57,18 @@ function randomUniquePadId() { // template rendering //---------------------------------------------------------------- -function renderTemplateAsString(filename, data) { +function findTemplate(filename, plugin) { + if (plugin != undefined) + return '/plugins/' + plugin + '/templates/' + filename; + else + return '/templates/' + filename; +} + +function renderTemplateAsString(filename, data, plugin) { data = data || {}; data.helpers = helpers; // global helpers - var f = "/templates/"+filename; + var f = findTemplate(filename, plugin); //"/templates/"+filename; if (! appjet.scopeCache.ejs) { appjet.scopeCache.ejs = {}; } @@ -75,22 +84,25 @@ function renderTemplateAsString(filename, data) { return html; } -function renderTemplate(filename, data) { - response.write(renderTemplateAsString(filename, data)); +function renderTemplate(filename, data, plugin) { + response.write(renderTemplateAsString(filename, data, plugin)); if (request.acceptsGzip) { response.setGzip(true); } } -function renderHtml(bodyFileName, data) { - var bodyHtml = renderTemplateAsString(bodyFileName, data); +function renderHtml(bodyFileName, data, plugin) { + var bodyHtml = renderTemplateAsString(bodyFileName, data, plugin); + bodyHtml = plugins.callHookStr("renderPageBodyPre", {bodyFileName:bodyFileName, data:data, plugin:plugin}) + + bodyHtml + + plugins.callHookStr("renderPageBodyPost", {bodyFileName:bodyFileName, data:data, plugin:plugin}); response.write(renderTemplateAsString("html.ejs", {bodyHtml: bodyHtml})); if (request.acceptsGzip) { response.setGzip(true); } } -function renderFramedHtml(contentHtml) { +function renderFramedHtml(contentHtml, plugin) { var getContentHtml; if (typeof(contentHtml) == 'function') { getContentHtml = contentHtml; @@ -109,52 +121,52 @@ function renderFramedHtml(contentHtml) { getContentHtml: getContentHtml, isProDomainRequest: isProDomainRequest(), renderGlobalProNotice: pro_utils.renderGlobalProNotice - }); + }, plugin); } -function renderFramed(bodyFileName, data) { +function renderFramed(bodyFileName, data, plugin) { function _getContentHtml() { - return renderTemplateAsString(bodyFileName, data); + return renderTemplateAsString(bodyFileName, data, plugin); } renderFramedHtml(_getContentHtml); } -function renderFramedError(error) { +function renderFramedError(error, plugin) { var content = DIV({className: 'fpcontent'}, DIV({style: "padding: 2em 1em;"}, DIV({style: "padding: 1em; border: 1px solid #faa; background: #fdd;"}, B("Error: "), error))); - renderFramedHtml(content); + renderFramedHtml(content, plugin); } -function renderNotice(bodyFileName, data) { - renderNoticeString(renderTemplateAsString(bodyFileName, data)); +function renderNotice(bodyFileName, data, plugin) { + renderNoticeString(renderTemplateAsString(bodyFileName, data, plugin), plugin); } -function renderNoticeString(contentHtml) { - renderFramed("notice.ejs", {content: contentHtml}); +function renderNoticeString(contentHtml, plugin) { + renderFramed("notice.ejs", {content: contentHtml}, plugin); } -function render404(noStop) { +function render404(noStop, plugin) { response.reset(); response.setStatusCode(404); renderFramedHtml(DIV({className: "fpcontent"}, DIV({style: "padding: 2em 1em;"}, DIV({style: "border: 1px solid #aaf; background: #def; padding: 1em; font-size: 150%;"}, - "404 not found: "+request.path)))); + "404 not found: "+request.path))), plugin); if (! noStop) { response.stop(); } } -function render500(ex) { +function render500(ex, plugin) { response.reset(); response.setStatusCode(500); var trace = null; if (ex && (!isProduction())) { trace = exceptionutils.getStackTracePlain(ex); } - renderFramed("500_body.ejs", {trace: trace}); + renderFramed("500_body.ejs", {trace: trace}, plugin); } function _renderEtherpadDotComHeader(data) { diff --git a/etherpad/src/main.js b/etherpad/src/main.js index 9cc1db2..8b08abb 100644 --- a/etherpad/src/main.js +++ b/etherpad/src/main.js @@ -34,6 +34,7 @@ import("etherpad.importexport.importexport"); import("etherpad.legacy_urls"); import("etherpad.control.aboutcontrol"); +import("etherpad.control.admin.pluginmanager"); import("etherpad.control.admincontrol"); import("etherpad.control.blogcontrol"); import("etherpad.control.connection_diagnostics_control"); @@ -68,6 +69,8 @@ import("etherpad.pad.dbwriter"); import("etherpad.pad.pad_migrations"); import("etherpad.pad.noprowatcher"); +import("etherpad.admin.plugins"); + jimport("java.lang.System.out.println"); serverhandlers.startupHandler = function() { @@ -92,6 +95,8 @@ serverhandlers.startupHandler = function() { team_billing.onStartup(); collabroom_server.onStartup(); readLatestSessionsFromDisk(); + + plugins.callHook('serverStartup'); }; serverhandlers.resetHandler = function() { @@ -99,6 +104,8 @@ serverhandlers.resetHandler = function() { } serverhandlers.shutdownHandler = function() { + plugins.callHook('serverShutdown'); + appjet.cache.shutdownHandlerIsRunning = true; log.callCatchingExceptions(writeSessionsToDisk); @@ -353,6 +360,8 @@ function handlePath() { // Default. Can be overridden in case of static files. response.neverCache(); + plugins.registerClientHandlerJS(); + // these paths are handled identically on all sites/subdomains. var commonDispatcher = new Dispatcher(); commonDispatcher.addLocations([ @@ -367,7 +376,7 @@ function handlePath() { [DirMatcher('/ep/unit-tests/'), forward(testcontrol)], [DirMatcher('/ep/pne-manual/'), forward(pne_manual_control)], [DirMatcher('/ep/pro-help/'), forward(pro_help_control)] - ]); + ].concat(plugins.callHook('handlePath'))); var etherpadDotComDispatcher = new Dispatcher(); etherpadDotComDispatcher.addLocations([ @@ -375,6 +384,7 @@ function handlePath() { [DirMatcher('/ep/beta-account/'), forward(pro_beta_control)], [DirMatcher('/ep/pro-signup/'), forward(pro_signup_control)], [DirMatcher('/ep/about/'), forward(aboutcontrol)], + [DirMatcher('/ep/admin/pluginmanager'), forward(pluginmanager)], [DirMatcher('/ep/admin/'), forward(admincontrol)], [DirMatcher('/ep/blog/posts/'), blogcontrol.render_post], [DirMatcher('/ep/blog/'), forward(blogcontrol)], diff --git a/infrastructure/framework-src/modules/execution.js b/infrastructure/framework-src/modules/execution.js index 1cec418..2f9d933 100644 --- a/infrastructure/framework-src/modules/execution.js +++ b/infrastructure/framework-src/modules/execution.js @@ -44,8 +44,11 @@ function fancyAssEval(initCode, mainCode) { 1); } var runner = Packages.net.appjet.oui.ScopeReuseManager.getEmpty(scalaF1(init)); + var requestWrapper = null; + if (request.underlying !== undefined) + requestWrapper = new Packages.net.appjet.oui.RequestWrapper(request.underlying); var ec = new Packages.net.appjet.oui.ExecutionContext( - new Packages.net.appjet.oui.RequestWrapper(request.underlying), + requestWrapper, null, runner); return Packages.net.appjet.oui.ExecutionContextUtils.withContext(ec, scalaF0(function() { diff --git a/infrastructure/framework-src/modules/sqlbase/sqlobj.js b/infrastructure/framework-src/modules/sqlbase/sqlobj.js index 4bc1263..e599c92 100644 --- a/infrastructure/framework-src/modules/sqlbase/sqlobj.js +++ b/infrastructure/framework-src/modules/sqlbase/sqlobj.js @@ -17,6 +17,7 @@ import("cache_utils.syncedWithCache"); import("sqlbase.sqlcommon.*"); import("jsutils.*"); +import("etherpad.log"); jimport("java.lang.System.out.println"); jimport("java.sql.Statement"); @@ -112,10 +113,13 @@ function _getJsValFromResultSet(rs, type, colName) { } else { r = null; } - } else if (type == java.sql.Types.INTEGER || + } else if (type == java.sql.Types.BIGINT || + type == java.sql.Types.INTEGER || type == java.sql.Types.SMALLINT || type == java.sql.Types.TINYINT) { r = rs.getInt(colName); + } else if (type == java.sql.Types.DECIMAL) { + r = rs.getFloat(colName); } else if (type == java.sql.Types.BIT) { r = rs.getBoolean(colName); } else { @@ -192,8 +196,9 @@ function _resultRowToJsObj(resultSet) { var metaData = resultSet.getMetaData(); var colCount = metaData.getColumnCount(); + for (var i = 1; i <= colCount; i++) { - var colName = metaData.getColumnName(i); + var colName = metaData.getColumnLabel(i); var type = metaData.getColumnType(i); resultObj[colName] = _getJsValFromResultSet(resultSet, type, colName); } @@ -338,6 +343,47 @@ function selectMulti(tableName, constraints, options) { }); } +function executeRaw(stmnt, params) { + return withConnection(function(conn) { + var pstmnt = conn.prepareStatement(stmnt); + return closing(pstmnt, function() { + for (var i = 0; i < params.length; i++) { + var v = params[i]; + + if (v === undefined) { + throw Error("value is undefined for key "+i); + } + + if (typeof(v) == 'object' && v.isnull) { + pstmnt.setNull(i+1, v.type); + } else if (typeof(v) == 'string') { + pstmnt.setString(i+1, v); + } else if (typeof(v) == 'number') { + pstmnt.setInt(i+1, v); + } else if (typeof(v) == 'boolean') { + pstmnt.setBoolean(i+1, v); + } else if (v.valueOf && v.getDate && v.getHours) { + pstmnt.setTimestamp(i+1, new java.sql.Timestamp(+v)); + } else { + throw Error("Cannot insert this type of javascript object: "+typeof(v)+" (key="+i+", value = "+v+")"); + } + } + + _qdebug(stmnt); + var resultSet = pstmnt.executeQuery(); + var resultArray = []; + + return closing(resultSet, function() { + while (resultSet.next()) { + resultArray.push(_resultRowToJsObj(resultSet)); + } + + return resultArray; + }); + }); + }); +} + /* returns number of rows updated */ function update(tableName, constraints, obj) { var objKeys = keys(obj); diff --git a/trunk/README.hooks b/trunk/README.hooks new file mode 100644 index 0000000..d15949c --- /dev/null +++ b/trunk/README.hooks @@ -0,0 +1,24 @@ +Hooks that plugins can provide + +All hooks must return either undefined/null or a list of return values. This might be an empty list or a list of just one value. + +handlePath + Registers new urls to serve + Parameters: None + Returns: Parameter suitable for Dispatcher +renderPageBodyPre + Adds extra html before the body of a page + Parameters: bodyFileName, data, plugin + Returns: String(s) of html +renderPageBodyPost + Adds extra html after the body of a page + Parameters: bodyFileName, data, plugin + Returns: String(s) of html +serverStartup + Run right after server startup + Parameters: None + Returns: None +serverShutdown + Run before server shutdown + Parameters: None + Returns: None diff --git a/trunk/etherpad/src/etherpad/admin/plugins.js b/trunk/etherpad/src/etherpad/admin/plugins.js new file mode 100644 index 0000000..41482fc --- /dev/null +++ b/trunk/etherpad/src/etherpad/admin/plugins.js @@ -0,0 +1,247 @@ +/** + * Copyright 2009 RedHog, Egil Möller <egil.moller@piratpartiet.se> + * + * Licensed 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. + */ + +import("faststatic"); +import("dispatch.{Dispatcher,PrefixMatcher,forward}"); + +import("etherpad.utils.*"); +import("etherpad.collab.server_utils"); +import("etherpad.globals.*"); +import("etherpad.log"); +import("etherpad.pad.padusers"); +import("etherpad.pro.pro_utils"); +import("etherpad.helpers"); +import("etherpad.pro.pro_accounts.getSessionProAccount"); +import("sqlbase.sqlbase"); +import("sqlbase.sqlcommon"); +import("sqlbase.sqlobj"); +import("exceptionutils"); +import("execution"); + +jimport("java.io.File", + "java.io.DataInputStream", + "java.io.FileInputStream", + "java.lang.Byte", + "java.io.FileReader", + "java.io.BufferedReader", + "net.appjet.oui.JarVirtualFile"); + +pluginsLoaded = false; +pluginModules = {}; +plugins = {}; +hooks = {}; +clientHooks = {}; + +function loadAvailablePlugin(pluginName) { + if (plugins[pluginName] != undefined) + return plugins[pluginName]; + + var pluginsDir = new Packages.java.io.File("src/plugins"); + + var pluginFile = new Packages.java.io.File(pluginsDir, pluginName + '/main.js'); + if (pluginFile.exists()) { + var pluginModulePath = pluginFile.getPath().replace(new RegExp("src/\(.*\)\.js"), "$1").replace("/", ".", "g"); + var importStmt = "import('" + pluginModulePath + "')"; + try { + var res = execution.fancyAssEval(importStmt, "main;"); + res = new res.init(); + return res; + } catch (e) { + log.info({errorLoadingPlugin:exceptionutils.getStackTracePlain(e)}); + } + } + return null; +} + +function loadAvailablePlugins() { + var pluginsDir = new Packages.java.io.File("src/plugins"); + + var pluginNames = pluginsDir.list(); + + for (i = 0; i < pluginNames.length; i++) { + var plugin = loadAvailablePlugin(pluginNames[i]); + if (plugin != null) + pluginModules[pluginNames[i]] = plugin + } +} + +function loadPluginHooks(pluginName) { + function registerHookNames(hookSet, type) { + return function (hook) { + var row = {hook:hook, type:type, plugin:pluginName}; + if (hookSet[hook] == undefined) hookSet[hook] = []; + hookSet[hook].push(row); + return row; + } + } + plugins[pluginName] = pluginModules[pluginName].hooks.map(registerHookNames(hooks, 'server')); + if (pluginModules[pluginName].client != undefined && pluginModules[pluginName].client.hooks != undefined) + plugins[pluginName] = plugins[pluginName].concat(pluginModules[pluginName].client.hooks.map(registerHookNames(clientHooks, 'client'))); +} + +function unloadPluginHooks(pluginName) { + for (var hookSet in [hooks, clientHooks]) + for (var hookName in hookSet) { + var hook = hookSet[hookName]; + for (i = hook.length - 1; i >= 0; i--) + if (hook[i].plugin == pluginName) + hook.splice(i, 1); + } + delete plugins[pluginName]; +} + +function loadInstalledHooks() { + var sql = '' + + 'select ' + + ' hook.name as hook, ' + + ' hook_type.name as type, ' + + ' plugin.name as plugin, ' + + ' plugin_hook.original_name as original ' + + 'from ' + + ' plugin ' + + ' left outer join plugin_hook on ' + + ' plugin.id = plugin_hook.plugin_id ' + + ' left outer join hook on ' + + ' plugin_hook.hook_id = hook.id ' + + ' left outer join hook_type on ' + + ' hook.type_id = hook_type.id ' + + 'order by hook.name, plugin.name'; + + var rows = sqlobj.executeRaw(sql, {}); + for (var i = 0; i < rows.length; i++) { + var row = rows[i]; + + if (plugins[row.plugin] == undefined) + plugins[row.plugin] = []; + plugins[row.plugin].push(row); + + var hookSet; + + if (row.type == 'server') + hookSet = hooks; + else if (row.type == 'client') + hookSet = clientHooks; + + if (hookSet[row.hook] == undefined) + hookSet[row.hook] = []; + if (row.hook != 'null') + hookSet[row.hook].push(row); + } +} + +function selectOrInsert(table, columns) { + var res = sqlobj.selectSingle(table, columns); + if (res !== null) + return res; + sqlobj.insert(table, columns); + return sqlobj.selectSingle(table, columns); +} + +function saveInstalledHooks(pluginName) { + var plugin = sqlobj.selectSingle('plugin', {name:pluginName}); + + if (plugin !== null) { + sqlobj.deleteRows('plugin_hook', {plugin_id:plugin.id}); + if (plugins[pluginName] === undefined) + sqlobj.deleteRows('plugin', {name:pluginName}); + } + + if (plugins[pluginName] !== undefined) { + if (plugin === null) + plugin = selectOrInsert('plugin', {name:pluginName}); + + for (var i = 0; i < plugins[pluginName].length; i++) { + var row = plugins[pluginName][i]; + + var hook_type = selectOrInsert('hook_type', {name:row.type}); + var hook = selectOrInsert('hook', {name:row.hook, type_id:hook_type.id}); + + sqlobj.insert("plugin_hook", {plugin_id:plugin.id, hook_id:hook.id}); + } + } +} + + +function loadPlugins() { + if (pluginsLoaded) return; + pluginsLoaded = true; + loadAvailablePlugins(); + loadInstalledHooks(); +} + + +/* User API */ +function enablePlugin(pluginName) { + loadPlugins(); + loadPluginHooks(pluginName); + saveInstalledHooks(pluginName); + try { + pluginModules[pluginName].install(); + } catch (e) { + unloadPluginHooks(pluginName); + saveInstalledHooks(pluginName); + throw e; + } + log.info({PLUGINS:plugins, HOOKS:hooks}); +} + +function disablePlugin(pluginName) { + loadPlugins(); + try { + pluginModules[pluginName].uninstall(); + } catch (e) { + log.info({errorUninstallingPlugin:exceptionutils.getStackTracePlain(e)}); + } + unloadPluginHooks(pluginName); + saveInstalledHooks(pluginName); + log.info({PLUGINS:plugins, HOOKS:hooks}); +} + +function registerClientHandlerJS() { + loadPlugins(); + for (pluginName in plugins) { + var plugin = pluginModules[pluginName]; + if (plugin.client !== undefined) { + helpers.includeJs("plugins/" + pluginName + "/main.js"); + if (plugin.client.modules != undefined) + for (j = 0; j < client.modules.length; j++) + helpers.includeJs("plugins/" + pluginName + "/" + plugin.client.modules[j] + ".js"); + } + } + helpers.addClientVars({hooks:clientHooks}); + helpers.includeJs("plugins.js"); +} + +function callHook(hookName, args) { + loadPlugins(); + if (hooks[hookName] === undefined) + return []; + var res = []; + for (i = 0; i < hooks[hookName].length; i++) { + var plugin = hooks[hookName][i]; + var pluginRes = pluginModules[plugin.plugin][plugin.original || hookName](args); + if (pluginRes != undefined && pluginRes != null) + res = res.concat(pluginRes); + } + return res; +} + +function callHookStr(hookName, args, sep, pre, post) { + if (sep == undefined) sep = ''; + if (pre == undefined) pre = ''; + if (post == undefined) post = ''; + return callHook(hookName, args).map(function (x) { return pre + x + post}).join(sep || ""); +} diff --git a/trunk/etherpad/src/etherpad/control/admin/pluginmanager.js b/trunk/etherpad/src/etherpad/control/admin/pluginmanager.js new file mode 100644 index 0000000..3fb017c --- /dev/null +++ b/trunk/etherpad/src/etherpad/control/admin/pluginmanager.js @@ -0,0 +1,65 @@ +/** + * Copyright 2009 RedHog, Egil Möller <egil.moller@piratpartiet.se> + * + * Licensed 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. + */ + +import("faststatic"); +import("dispatch.{Dispatcher,PrefixMatcher,forward}"); + +import("etherpad.utils.*"); +import("etherpad.collab.server_utils"); +import("etherpad.globals.*"); +import("etherpad.log"); +import("etherpad.pad.padusers"); +import("etherpad.pro.pro_utils"); +import("etherpad.helpers"); +import("etherpad.pro.pro_accounts.getSessionProAccount"); +import("etherpad.admin.plugins"); + + +function onRequest() { + plugins.loadPlugins(); + + if (request.params.action == 'install') { + plugins.enablePlugin(request.params.plugin); + } else if (request.params.action == 'uninstall') { + plugins.disablePlugin(request.params.plugin); + } else if (request.params.action == 'reinstall') { + plugins.disablePlugin(request.params.plugin); + plugins.enablePlugin(request.params.plugin); + } + + helpers.addClientVars({ + userAgent: request.headers["User-Agent"], + debugEnabled: request.params.djs, + clientIp: request.clientAddr, + colorPalette: COLOR_PALETTE, + serverTimestamp: +(new Date), + isProPad: pro_utils.isProDomainRequest(), + userIsGuest: padusers.isGuest(padusers.getUserId()), + userId: padusers.getUserId(), + }); + + renderHtml("admin/pluginmanager.ejs", + { + pluginModules: plugins.pluginModules, + plugins: plugins.plugins, + config: appjet.config, + bodyClass: 'nonpropad', + isPro: pro_utils.isProDomainRequest(), + isProAccountHolder: pro_utils.isProDomainRequest() && ! padusers.isGuest(padusers.getUserId()), + account: getSessionProAccount(), // may be falsy + }); + return true; +} diff --git a/trunk/etherpad/src/plugins/kafoo/main.js b/trunk/etherpad/src/plugins/kafoo/main.js new file mode 100644 index 0000000..f645576 --- /dev/null +++ b/trunk/etherpad/src/plugins/kafoo/main.js @@ -0,0 +1,16 @@ +import("etherpad.log"); + +function init() { + this.hooks = []; + this.description = 'KaBar plugin'; + this.install = install; + this.uninstall = uninstall; +} + +function install() { + log.info("Installing testplugin"); +} + +function uninstall() { + log.info("Uninstalling testplugin"); +} diff --git a/trunk/etherpad/src/plugins/testplugin/controllers/testplugin.js b/trunk/etherpad/src/plugins/testplugin/controllers/testplugin.js new file mode 100644 index 0000000..0c79e06 --- /dev/null +++ b/trunk/etherpad/src/plugins/testplugin/controllers/testplugin.js @@ -0,0 +1,57 @@ +/** + * Copyright 2009 RedHog, Egil Möller <egil.moller@piratpartiet.se> + * + * Licensed 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. + */ + +import("faststatic"); +import("dispatch.{Dispatcher,PrefixMatcher,forward}"); + +import("etherpad.utils.*"); +import("etherpad.collab.server_utils"); +import("etherpad.globals.*"); +import("etherpad.log"); +import("etherpad.pad.padusers"); +import("etherpad.pro.pro_utils"); +import("etherpad.helpers"); +import("etherpad.pro.pro_accounts.getSessionProAccount"); +import("sqlbase.sqlbase"); +import("sqlbase.sqlcommon"); +import("sqlbase.sqlobj"); + + +function onRequest() { + var isPro = pro_utils.isProDomainRequest(); + var userId = padusers.getUserId(); + + helpers.addClientVars({ + userAgent: request.headers["User-Agent"], + debugEnabled: request.params.djs, + clientIp: request.clientAddr, + colorPalette: COLOR_PALETTE, + serverTimestamp: +(new Date), + isProPad: isPro, + userIsGuest: padusers.isGuest(userId), + userId: userId, + }); + + var isProUser = (isPro && ! padusers.isGuest(userId)); + + renderHtml("testplugin.ejs", + { + isPro: isPro, + isProAccountHolder: isProUser, + account: getSessionProAccount(), // may be falsy + }, 'testplugin'); + return true; +} diff --git a/trunk/etherpad/src/plugins/testplugin/hooks.js b/trunk/etherpad/src/plugins/testplugin/hooks.js new file mode 100644 index 0000000..493a2c2 --- /dev/null +++ b/trunk/etherpad/src/plugins/testplugin/hooks.js @@ -0,0 +1,15 @@ +import("etherpad.log"); +import("dispatch.{Dispatcher,PrefixMatcher,forward}"); +import("plugins.testplugin.controllers.testplugin"); + +function serverStartup() { + log.info("Server startup for testplugin"); +} + +function serverShutdown() { + log.info("Server shutdown for testplugin"); +} + +function handlePath() { + return [[PrefixMatcher('/ep/testplugin/'), forward(testplugin)]]; +} diff --git a/trunk/etherpad/src/plugins/testplugin/main.js b/trunk/etherpad/src/plugins/testplugin/main.js new file mode 100644 index 0000000..49b447c --- /dev/null +++ b/trunk/etherpad/src/plugins/testplugin/main.js @@ -0,0 +1,23 @@ +import("etherpad.log"); +import("plugins.testplugin.hooks"); +import("plugins.testplugin.static.js.main"); + +function init() { + this.hooks = ['serverStartup', 'serverShutdown', 'handlePath']; + this.client = new main.init(); + this.description = 'Test Plugin'; + this.serverStartup = hooks.serverStartup; + this.serverShutdown = hooks.serverShutdown; + this.handlePath = hooks.handlePath; + this.install = install; + this.uninstall = uninstall; +} + +function install() { + log.info("Installing testplugin"); +} + +function uninstall() { + log.info("Uninstalling testplugin"); +} + diff --git a/trunk/etherpad/src/plugins/testplugin/static/js/main.js b/trunk/etherpad/src/plugins/testplugin/static/js/main.js new file mode 100644 index 0000000..f08b8f7 --- /dev/null +++ b/trunk/etherpad/src/plugins/testplugin/static/js/main.js @@ -0,0 +1,11 @@ +function init() { + this.hooks = ['kafoo']; + this.kafoo = kafoo; +} + +function kafoo() { + alert('hej'); +} + +/* used on the client side only */ +testplugin = new init(); diff --git a/trunk/etherpad/src/plugins/testplugin/static/js/test.js b/trunk/etherpad/src/plugins/testplugin/static/js/test.js new file mode 100644 index 0000000..0f30cd9 --- /dev/null +++ b/trunk/etherpad/src/plugins/testplugin/static/js/test.js @@ -0,0 +1 @@ +callHook("kafoo"); diff --git a/trunk/etherpad/src/plugins/testplugin/templates/testplugin.ejs b/trunk/etherpad/src/plugins/testplugin/templates/testplugin.ejs new file mode 100644 index 0000000..f70ca8d --- /dev/null +++ b/trunk/etherpad/src/plugins/testplugin/templates/testplugin.ejs @@ -0,0 +1,29 @@ +<% /* Copyright 2009 Google Inc. + +Licensed 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. */ %> +<% + helpers.setHtmlTitle("Test plugin"); + helpers.setBodyId("padbody"); + helpers.addBodyClass("limwidth nonpropad nonprouser"); + helpers.includeCss("pad2_ejs.css"); + helpers.setRobotsPolicy({index: false, follow: false}) + helpers.includeJQuery(); + helpers.includeCometJs(); + helpers.includeJs("json2.js"); + helpers.includeJs("plugins/testplugin/test.js"); + helpers.addToHead('\n<style type="text/css" title="dynamicsyntax"></style>\n'); +%> + +<div id="padpage"> + Welcome to the test plugin +</div> diff --git a/trunk/etherpad/src/static/js/plugins.js b/trunk/etherpad/src/static/js/plugins.js new file mode 100644 index 0000000..6d8804e --- /dev/null +++ b/trunk/etherpad/src/static/js/plugins.js @@ -0,0 +1,19 @@ +function callHook(hookName, args) { + if (clientVars.hooks[hookName] === undefined) + return []; + var res = []; + for (i = 0; i < clientVars.hooks[hookName].length; i++) { + var plugin = clientVars.hooks[hookName][i]; + var pluginRes = eval(plugin.plugin)[plugin.original || hookName](args); + if (pluginRes != undefined && pluginRes != null) + res = res.concat(pluginRes); + } + return res; +} + +function callHookStr(hookName, args, sep, pre, post) { + if (sep == undefined) sep = ''; + if (pre == undefined) pre = ''; + if (post == undefined) post = ''; + return callHook(hookName, args).map(function (x) { return pre + x + post}).join(sep || ""); +} diff --git a/trunk/etherpad/src/templates/admin/pluginmanager.ejs b/trunk/etherpad/src/templates/admin/pluginmanager.ejs new file mode 100644 index 0000000..4e08fc9 --- /dev/null +++ b/trunk/etherpad/src/templates/admin/pluginmanager.ejs @@ -0,0 +1,126 @@ +<% /* Copyright 2009 Google Inc. + +Licensed 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. */ %> +<% + helpers.setHtmlTitle("Browse tags"); + helpers.setBodyId("padbody"); + helpers.addBodyClass("limwidth nonpropad nonprouser"); + helpers.includeCss("pad2_ejs.css"); + helpers.setRobotsPolicy({index: false, follow: false}) + helpers.includeJQuery(); + helpers.includeCometJs(); + helpers.includeJs("json2.js"); + helpers.addToHead('\n<style type="text/css" title="dynamicsyntax"></style>\n'); + + function inArray(item, arr) { + for (var i = 0; i < arr.length; i++) + if (arr[i] == item) + return true; + return false; + } +%> + +<div id="padpage"> + <div id="padtop"> + <div id="topbar" style="margin: 7px; margin-top: 0px;"> + <div id="topbarleft"><!-- --></div> + <div id="topbarright"><!-- --></div> + <div id="topbarcenter"><a href="/" id="topbaretherpad">EtherPad</a></div> + <% if (isProAccountHolder) { %> + <div id="accountnav"><%= toHTML(account.email) %><a href="/ep/account/sign-out">(sign out)</a></div> + <% } else if (isPro) { %> + <div id="accountnav"><a href="<%= signinUrl %>">sign in</a></div> + <% } %> + </div> + </div> + <div id="docbar" class="docbar-public"> + <div id="docbarleft"><!-- --></div> + <div title="Browse pads by tag" id="docbarpadtitle"><span>Browse tags</span></div> + + <div id="docbaroptions-outer"><a href="javascript:void(0)" id="docbaroptions">Pad Options</a></div> + <div id="docbarsavedrevs-outer"><a href="javascript:void(0)" id="docbarsavedrevs">Saved revisions</a></div> + <div id="docbarimpexp-outer"><a href="javascript:void(0)" id="docbarimpexp">Import/Export</a></div> + <div id="docbarslider-outer"><a target="_blank" href="/ep/pad/view/xx/latest" id="docbarslider">Time Slider</a></div> + + </div> + </div> + <div id="padmain"> + + <div id="padsidebar"> + <div id="padusers"> + </div> + + <div id="hdraggie"><!-- --></div> + + <div id="padchat"></div> + </div> <!-- /padsidebar --> + + <div id="padeditor"> + <div id="editbar" class="enabledtoolbar"> + <div id="editbarleft"><!-- --></div> + <div id="editbarright"><!-- --></div> + + <div id="editbarinner"> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('bold'));" class="editbarbutton bold" title="Bold (ctrl-B)"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('italic'));" class="editbarbutton italic" title="Italics (ctrl-I)"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('underline'));" class="editbarbutton underline" title="Underline (ctrl-U)"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('strikethrough'));" class="editbarbutton strikethrough" title="Strikethrough"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('clearauthorship'));" class="editbarbutton clearauthorship" title="Clear Authorship Colors"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('undo'));" class="editbarbutton undo" title="Undo (ctrl-Z)"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('redo'));" class="editbarbutton redo" title="Redo (ctrl-Y)"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('insertunorderedlist'));" class="editbarbutton insertunorderedlist" title="Toggle Bullet List"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('indent'));" class="editbarbutton indent" title="Indent List"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('outdent'));" class="editbarbutton outdent" title="Unindent List"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('save'));" class="editbarbutton save" title="Save Revision"> </a> + </div> + </div> + <div style="height: 268px;" id="editorcontainerbox"> + <div id="editorcontainer" style="padding:5pt; height: 600pt;"> + <h1>Plugin manager</h1> + <table> + <tr> + <th>Module name</th> + <th>Status</th> + <th></th> + </tr> + <% for (var plugin in pluginModules) { %> + <tr> + <td><%= pluginModules[plugin].description %></td> + <td> + <% if (plugins[plugin] !== undefined) { %> + Installed + <% } else { %> + Not installed + <% } %> + </td> + <td> + <% if (plugins[plugin] !== undefined) { %> + <a href="/ep/admin/pluginmanager/?plugin=<%= plugin %>&action=uninstall">Uninstall</a> + <a href="/ep/admin/pluginmanager/?plugin=<%= plugin %>&action=reinstall">Reinstall</a> + <% } else { %> + <a href="/ep/admin/pluginmanager/?plugin=<%= plugin %>&action=install">Install</a> + <% } %> + </td> + </tr> + <% } %> + </table> + </div> + </div> + </div><!-- /padeditor --> + + <div id="bottomarea"> + <div id="widthprefcheck" class="widthprefunchecked"><!-- --></div> + <div id="sidebarcheck" class="sidebarchecked"><!-- --></div> + </div> + </div> +</div> |