summaryrefslogtreecommitdiffstats
path: root/trunk/etherpad/src/etherpad/control/pro
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/etherpad/src/etherpad/control/pro')
-rw-r--r--trunk/etherpad/src/etherpad/control/pro/account_control.js369
-rw-r--r--trunk/etherpad/src/etherpad/control/pro/admin/account_manager_control.js260
-rw-r--r--trunk/etherpad/src/etherpad/control/pro/admin/license_manager_control.js128
-rw-r--r--trunk/etherpad/src/etherpad/control/pro/admin/pro_admin_control.js283
-rw-r--r--trunk/etherpad/src/etherpad/control/pro/admin/pro_config_control.js54
-rw-r--r--trunk/etherpad/src/etherpad/control/pro/admin/team_billing_control.js447
-rw-r--r--trunk/etherpad/src/etherpad/control/pro/pro_main_control.js150
-rw-r--r--trunk/etherpad/src/etherpad/control/pro/pro_padlist_control.js200
8 files changed, 1891 insertions, 0 deletions
diff --git a/trunk/etherpad/src/etherpad/control/pro/account_control.js b/trunk/etherpad/src/etherpad/control/pro/account_control.js
new file mode 100644
index 0000000..031dbe6
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/control/pro/account_control.js
@@ -0,0 +1,369 @@
+/**
+ * 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.
+ */
+
+import("stringutils");
+import("stringutils.*");
+import("funhtml.*");
+import("email.sendEmail");
+import("cache_utils.syncedWithCache");
+
+import("etherpad.helpers");
+import("etherpad.utils.*");
+import("etherpad.sessions.getSession");
+import("etherpad.pro.pro_accounts");
+import("etherpad.pro.pro_accounts.getSessionProAccount");
+import("etherpad.pro.domains");
+import("etherpad.pro.pro_utils");
+import("etherpad.pro.pro_account_auto_signin");
+import("etherpad.pro.pro_config");
+import("etherpad.pad.pad_security");
+import("etherpad.pad.padutils");
+import("etherpad.pad.padusers");
+import("etherpad.collab.collab_server");
+
+function onRequest() {
+ if (!getSession().tempFormData) {
+ getSession().tempFormData = {};
+ }
+
+ return false; // path not handled here
+}
+
+//--------------------------------------------------------------------------------
+// helpers
+//--------------------------------------------------------------------------------
+
+function _redirOnError(m, clearQuery) {
+ if (m) {
+ getSession().accountFormError = m;
+
+ var dest = request.url;
+ if (clearQuery) {
+ dest = request.path;
+ }
+ response.redirect(dest);
+ }
+}
+
+function setSigninNotice(m) {
+ getSession().accountSigninNotice = m;
+}
+
+function setSessionError(m) {
+ getSession().accountFormError = m;
+}
+
+function _topDiv(id, name) {
+ var m = getSession()[name];
+ if (m) {
+ delete getSession()[name];
+ return DIV({id: id}, m);
+ } else {
+ return '';
+ }
+}
+
+function _messageDiv() { return _topDiv('account-message', 'accountMessage'); }
+function _errorDiv() { return _topDiv('account-error', 'accountFormError'); }
+function _signinNoticeDiv() { return _topDiv('signin-notice', 'accountSigninNotice'); }
+
+function _renderTemplate(name, data) {
+ data.messageDiv = _messageDiv;
+ data.errorDiv = _errorDiv;
+ data.signinNotice = _signinNoticeDiv;
+ data.tempFormData = getSession().tempFormData;
+ renderFramed('pro/account/'+name+'.ejs', data);
+}
+
+//----------------------------------------------------------------
+// /ep/account/
+//----------------------------------------------------------------
+
+function render_main_get() {
+ _renderTemplate('my-account', {
+ account: getSessionProAccount(),
+ changePass: getSession().changePass
+ });
+}
+
+function render_update_info_get() {
+ response.redirect('/ep/account/');
+}
+
+function render_update_info_post() {
+ var fullName = request.params.fullName;
+ var email = trim(request.params.email);
+
+ getSession().tempFormData.email = email;
+ getSession().tempFormData.fullName = fullName;
+
+ _redirOnError(pro_accounts.validateEmail(email));
+ _redirOnError(pro_accounts.validateFullName(fullName));
+
+ pro_accounts.setEmail(getSessionProAccount(), email);
+ pro_accounts.setFullName(getSessionProAccount(), fullName);
+
+ getSession().accountMessage = "Info updated.";
+ response.redirect('/ep/account/');
+}
+
+function render_update_password_get() {
+ response.redirect('/ep/account/');
+}
+
+function render_update_password_post() {
+ var password = request.params.password;
+ var passwordConfirm = request.params.passwordConfirm;
+
+ if (password != passwordConfirm) { _redirOnError('Passwords did not match.'); }
+
+ _redirOnError(pro_accounts.validatePassword(password));
+
+ pro_accounts.setPassword(getSessionProAccount(), password);
+
+ if (getSession().changePass) {
+ delete getSession().changePass;
+ response.redirect('/');
+ }
+
+ getSession().accountMessage = "Password updated.";
+ response.redirect('/ep/account/');
+}
+
+//--------------------------------------------------------------------------------
+// signin/signout
+//--------------------------------------------------------------------------------
+
+function render_sign_in_get() {
+ if (request.params.uid && request.params.tp) {
+ var m = pro_accounts.authenticateTempSignIn(Number(request.params.uid), request.params.tp);
+ if (m) {
+ getSession().accountFormError = m;
+ response.redirect('/ep/account/');
+ }
+ }
+ if (request.params.instantSigninKey) {
+ _attemptInstantSignin(request.params.instantSigninKey);
+ }
+ if (getSession().recentlySignedOut && getSession().accountFormError) {
+ delete getSession().accountFormError;
+ delete getSession().recentlySignedOut;
+ }
+ // Note: must check isAccountSignedIn before calling checkAutoSignin()!
+ if (pro_accounts.isAccountSignedIn()) {
+ _redirectToPostSigninDestination();
+ }
+ pro_account_auto_signin.checkAutoSignin();
+ var domainRecord = domains.getRequestDomainRecord();
+ var showGuestBox = false;
+ if (request.params.guest && request.params.padId) {
+ showGuestBox = true;
+ }
+ _renderTemplate('signin', {
+ domain: pro_utils.getFullProDomain(),
+ siteName: toHTML(pro_config.getConfig().siteName),
+ email: getSession().tempFormData.email || "",
+ password: getSession().tempFormData.password || "",
+ rememberMe: getSession().tempFormData.rememberMe || false,
+ showGuestBox: showGuestBox,
+ localPadId: request.params.padId
+ });
+}
+
+function _attemptInstantSignin(key) {
+ // See src/etherpad/control/global_pro_account_control.js
+ var email = null;
+ var password = null;
+ syncedWithCache('global_signin_passwords', function(c) {
+ if (c[key]) {
+ email = c[key].email;
+ password = c[key].password;
+ }
+ delete c[key];
+ });
+ getSession().tempFormData.email = email;
+ _redirOnError(pro_accounts.authenticateSignIn(email, password), true);
+}
+
+function render_sign_in_post() {
+ var email = trim(request.params.email);
+ var password = request.params.password;
+
+ getSession().tempFormData.email = email;
+ getSession().tempFormData.rememberMe = request.params.rememberMe;
+
+ _redirOnError(pro_accounts.authenticateSignIn(email, password));
+ pro_account_auto_signin.setAutoSigninCookie(request.params.rememberMe);
+ _redirectToPostSigninDestination();
+}
+
+function render_guest_sign_in_get() {
+ var localPadId = request.params.padId;
+ var domainId = domains.getRequestDomainId();
+ var globalPadId = padutils.makeGlobalId(domainId, localPadId);
+ var userId = padusers.getUserId();
+
+ pro_account_auto_signin.checkAutoSignin();
+ pad_security.clearKnockStatus(userId, globalPadId);
+
+ _renderTemplate('signin-guest', {
+ localPadId: localPadId,
+ errorMessage: getSession().guestAccessError,
+ siteName: toHTML(pro_config.getConfig().siteName),
+ guestName: padusers.getUserName() || ""
+ });
+}
+
+function render_guest_sign_in_post() {
+ function _err(m) {
+ if (m) {
+ getSession().guestAccessError = m;
+ response.redirect(request.url);
+ }
+ }
+ var displayName = request.params.guestDisplayName;
+ var localPadId = request.params.localPadId;
+ if (!(displayName && displayName.length > 0)) {
+ _err("Please enter a display name");
+ }
+ getSession().guestDisplayName = displayName;
+ response.redirect('/ep/account/guest-knock?padId='+encodeURIComponent(localPadId)+
+ "&guestDisplayName="+encodeURIComponent(displayName));
+}
+
+function render_guest_knock_get() {
+ var localPadId = request.params.padId;
+ helpers.addClientVars({
+ localPadId: localPadId,
+ guestDisplayName: request.params.guestDisplayName,
+ padUrl: "http://"+httpHost(request.host)+"/"+localPadId
+ });
+ _renderTemplate('guest-knock', {});
+}
+
+function render_guest_knock_post() {
+ var localPadId = request.params.padId;
+ var displayName = request.params.guestDisplayName;
+ var domainId = domains.getRequestDomainId();
+ var globalPadId = padutils.makeGlobalId(domainId, localPadId);
+ var userId = padusers.getUserId();
+
+ response.setContentType("text/plain; charset=utf-8");
+ // has the knock already been answsered?
+ var currentAnswer = pad_security.getKnockAnswer(userId, globalPadId);
+ if (currentAnswer) {
+ response.write(currentAnswer);
+ } else {
+ collab_server.guestKnock(globalPadId, userId, displayName);
+ response.write("wait");
+ }
+}
+
+function _redirectToPostSigninDestination() {
+ var cont = request.params.cont;
+ if (!cont) { cont = '/'; }
+ response.redirect(cont);
+}
+
+function render_sign_out() {
+ pro_account_auto_signin.setAutoSigninCookie(false);
+ pro_accounts.signOut();
+ delete getSession().padPasswordAuth;
+ getSession().recentlySignedOut = true;
+ response.redirect("/");
+}
+
+//--------------------------------------------------------------------------------
+// create-admin-account (eepnet only)
+//--------------------------------------------------------------------------------
+
+function render_create_admin_account_get() {
+ if (pro_accounts.doesAdminExist()) {
+ renderFramedError("An admin account already exists on this domain.");
+ response.stop();
+ }
+ _renderTemplate('create-admin-account', {});
+}
+
+function render_create_admin_account_post() {
+ var email = trim(request.params.email);
+ var password = request.params.password;
+ var passwordConfirm = request.params.passwordConfirm;
+ var fullName = request.params.fullName;
+
+ getSession().tempFormData.email = email;
+ getSession().tempFormData.fullName = fullName;
+
+ if (password != passwordConfirm) { _redirOnError('Passwords did not match.'); }
+
+ _redirOnError(pro_accounts.validateEmail(email));
+ _redirOnError(pro_accounts.validateFullName(fullName));
+ _redirOnError(pro_accounts.validatePassword(password));
+
+ pro_accounts.createNewAccount(null, fullName, email, password, true);
+
+ var u = pro_accounts.getAccountByEmail(email, null);
+
+ // TODO: should we send a welcome email here?
+ //pro_accounts.sendWelcomeEmail(u);
+
+ _redirOnError(pro_accounts.authenticateSignIn(email, password));
+
+ response.redirect("/");
+}
+
+
+//--------------------------------------------------------------------------------
+// forgot password
+//--------------------------------------------------------------------------------
+
+function render_forgot_password_get() {
+ if (request.params.instantSubmit && request.params.email) {
+ render_forgot_password_post();
+ } else {
+ _renderTemplate('forgot-password', {
+ email: getSession().tempFormData.email || ""
+ });
+ }
+}
+
+function render_forgot_password_post() {
+ var email = trim(request.params.email);
+
+ getSession().tempFormData.email = email;
+
+ var u = pro_accounts.getAccountByEmail(email, null);
+ if (!u) {
+ _redirOnError("Account not found: "+email);
+ }
+
+ var tempPass = stringutils.randomString(10);
+ pro_accounts.setTempPassword(u, tempPass);
+
+ var subj = "EtherPad: Request to reset your password on "+request.domain;
+ var body = renderTemplateAsString('pro/account/forgot-password-email.ejs', {
+ account: u,
+ recoverUrl: pro_accounts.getTempSigninUrl(u, tempPass)
+ });
+ var fromAddr = pro_utils.getEmailFromAddr();
+ sendEmail(u.email, fromAddr, subj, {}, body);
+
+ getSession().accountMessage = "An email has been sent to "+u.email+" with instructions to reset the password.";
+ response.redirect(request.path);
+}
+
+
+
diff --git a/trunk/etherpad/src/etherpad/control/pro/admin/account_manager_control.js b/trunk/etherpad/src/etherpad/control/pro/admin/account_manager_control.js
new file mode 100644
index 0000000..8f93b2e
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/control/pro/admin/account_manager_control.js
@@ -0,0 +1,260 @@
+/**
+ * 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.
+ */
+
+import("funhtml.*");
+import("stringutils");
+import("stringutils.*");
+import("email.sendEmail");
+
+import("etherpad.globals.*");
+import("etherpad.utils.*");
+import("etherpad.sessions.getSession");
+
+import("etherpad.control.pro.admin.pro_admin_control");
+
+import("etherpad.pne.pne_utils");
+import("etherpad.pro.pro_accounts");
+import("etherpad.pro.pro_accounts.getSessionProAccount");
+import("etherpad.pro.pro_utils");
+import("etherpad.pro.pro_config");
+import("etherpad.pro.domains");
+import("etherpad.billing.team_billing");
+
+jimport("java.lang.System.out.println");
+
+function _err(m) {
+ if (m) {
+ getSession().accountManagerError = m;
+ response.redirect(request.path);
+ }
+}
+
+function _renderTopDiv(mid, htmlId) {
+ var m = getSession()[mid];
+ if (m) {
+ delete getSession()[mid];
+ return DIV({id: htmlId}, m);
+ } else {
+ return '';
+ }
+}
+
+function _errorDiv() { return _renderTopDiv('accountManagerError', 'error-message'); }
+function _messageDiv() { return _renderTopDiv('accountManagerMessage', 'message'); }
+function _warningDiv() { return _renderTopDiv('accountManagerWarning', 'warning'); }
+
+function onRequest() {
+ var parts = request.path.split('/');
+
+ function dispatchAccountAction(action, handlerGet, handlerPost) {
+ if ((parts[4] == action) && (isNumeric(parts[5]))) {
+ if (request.isGet) { handlerGet(+parts[5]); }
+ if (request.isPost) { handlerPost(+parts[5]); }
+ return true;
+ }
+ return false;
+ }
+
+ if (dispatchAccountAction('account', render_account_get, render_account_post)) {
+ return true;
+ }
+ if (dispatchAccountAction('delete-account', render_delete_account_get, render_delete_account_post)) {
+ return true;
+ };
+
+ return false;
+}
+
+function render_main() {
+ var accountList = pro_accounts.listAllDomainAccounts();
+ pro_admin_control.renderAdminPage('account-manager', {
+ accountList: accountList,
+ messageDiv: _messageDiv,
+ warningDiv: _warningDiv
+ });
+}
+
+function render_new_get() {
+ pro_admin_control.renderAdminPage('new-account', {
+ oldData: getSession().accountManagerFormData || {},
+ stringutils: stringutils,
+ errorDiv: _errorDiv
+ });
+}
+
+function _ensureBillingOK() {
+ var activeAccounts = pro_accounts.getCachedActiveCount(domains.getRequestDomainId());
+ if (activeAccounts < PRO_FREE_ACCOUNTS) {
+ return;
+ }
+
+ var status = team_billing.getDomainStatus(domains.getRequestDomainId());
+ if (!((status == team_billing.CURRENT)
+ || (status == team_billing.PAST_DUE))) {
+ _err(SPAN(
+ "A payment profile is required to create more than ", PRO_FREE_ACCOUNTS,
+ " accounts. ",
+ A({href: "/ep/admin/billing/", id: "billinglink"}, "Manage billing")));
+ }
+}
+
+function render_new_post() {
+ if (request.params.cancel) {
+ response.redirect('/ep/admin/account-manager/');
+ }
+
+ _ensureBillingOK();
+
+ var fullName = request.params.fullName;
+ var email = trim(request.params.email);
+ var tempPass = request.params.tempPass;
+ var makeAdmin = !!request.params.makeAdmin;
+
+ getSession().accountManagerFormData = {
+ fullName: fullName,
+ email: email,
+ tempPass: tempPass,
+ makeAdmin: makeAdmin
+ };
+
+ // validation
+ if (!tempPass) {
+ tempPass = stringutils.randomString(6);
+ }
+
+ _err(pro_accounts.validateEmail(email));
+ _err(pro_accounts.validateFullName(fullName));
+ _err(pro_accounts.validatePassword(tempPass));
+
+ var existingAccount = pro_accounts.getAccountByEmail(email, null);
+ if (existingAccount) {
+ _err("There is already a account with that email address.");
+ }
+
+ pro_accounts.createNewAccount(null, fullName, email, tempPass, makeAdmin);
+ var account = pro_accounts.getAccountByEmail(email, null);
+
+ pro_accounts.setTempPassword(account, tempPass);
+ sendWelcomeEmail(account, tempPass);
+
+ delete getSession().accountManagerFormData;
+ getSession().accountManagerMessage = "Account "+fullName+" ("+email+") created successfully.";
+ response.redirect('/ep/admin/account-manager/');
+}
+
+function sendWelcomeEmail(account, tempPass) {
+ var subj = "Welcome to EtherPad on "+pro_utils.getFullProDomain()+"!";
+ var toAddr = account.email;
+ var fromAddr = pro_utils.getEmailFromAddr();
+
+ var body = renderTemplateAsString('pro/account/account-welcome-email.ejs', {
+ account: account,
+ adminAccount: getSessionProAccount(),
+ signinLink: pro_accounts.getTempSigninUrl(account, tempPass),
+ toEmail: toAddr,
+ siteName: pro_config.getConfig().siteName
+ });
+ try {
+ sendEmail(toAddr, fromAddr, subj, {}, body);
+ } catch (ex) {
+ var d = DIV();
+ d.push(P("Warning: unable to send welcome email."));
+ if (pne_utils.isPNE()) {
+ d.push(P("Perhaps you have not ",
+ A({href: '/ep/admin/pne-config'}, "Configured SMTP on this server", "?")));
+ }
+ getSession().accountManagerWarning = d;
+ }
+}
+
+// Managing a single account.
+function render_account_get(accountId) {
+ var account = pro_accounts.getAccountById(accountId);
+ if (!account) {
+ response.write("Account not found.");
+ return true;
+ }
+ pro_admin_control.renderAdminPage('manage-account', {
+ account: account,
+ errorDiv: _errorDiv,
+ warningDiv: _warningDiv
+ });
+}
+
+function render_account_post(accountId) {
+ if (request.params.cancel) {
+ response.redirect('/ep/admin/account-manager/');
+ }
+ var newFullName = request.params.newFullName;
+ var newEmail = request.params.newEmail;
+ var newIsAdmin = !!request.params.newIsAdmin;
+
+ _err(pro_accounts.validateEmail(newEmail));
+ _err(pro_accounts.validateFullName(newFullName));
+
+ if ((!newIsAdmin) && (accountId == getSessionProAccount().id)) {
+ _err("You cannot remove your own administrator privileges.");
+ }
+
+ var account = pro_accounts.getAccountById(accountId);
+ if (!account) {
+ response.write("Account not found.");
+ return true;
+ }
+
+ pro_accounts.setEmail(account, newEmail);
+ pro_accounts.setFullName(account, newFullName);
+ pro_accounts.setIsAdmin(account, newIsAdmin);
+
+ getSession().accountManageMessage = "Info updated.";
+ response.redirect('/ep/admin/account-manager/');
+}
+
+function render_delete_account_get(accountId) {
+ var account = pro_accounts.getAccountById(accountId);
+ if (!account) {
+ response.write("Account not found.");
+ return true;
+ }
+ pro_admin_control.renderAdminPage('delete-account', {
+ account: account,
+ errorDiv: _errorDiv
+ });
+}
+
+function render_delete_account_post(accountId) {
+ if (request.params.cancel) {
+ response.redirect("/ep/admin/account-manager/account/"+accountId);
+ }
+
+ if (accountId == getSessionProAccount().id) {
+ getSession().accountManagerError = "You cannot delete your own account.";
+ response.redirect("/ep/admin/account-manager/account/"+accountId);
+ }
+
+ var account = pro_accounts.getAccountById(accountId);
+ if (!account) {
+ response.write("Account not found.");
+ return true;
+ }
+
+ pro_accounts.setDeleted(account);
+ getSession().accountManagerMessage = "The account "+account.fullName+" <"+account.email+"> has been deleted.";
+ response.redirect("/ep/admin/account-manager/");
+}
+
+
+
diff --git a/trunk/etherpad/src/etherpad/control/pro/admin/license_manager_control.js b/trunk/etherpad/src/etherpad/control/pro/admin/license_manager_control.js
new file mode 100644
index 0000000..ca6d6a6
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/control/pro/admin/license_manager_control.js
@@ -0,0 +1,128 @@
+/**
+ * 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.
+ */
+
+import("fileutils.writeRealFile");
+import("stringutils");
+
+import("etherpad.licensing");
+import("etherpad.sessions.getSession");
+import("etherpad.utils.*");
+import("etherpad.pne.pne_utils");
+
+import("etherpad.control.pro.admin.pro_admin_control");
+
+jimport("java.lang.System.out.println");
+
+//----------------------------------------------------------------
+// license manager
+//----------------------------------------------------------------
+
+function getPath() {
+ return '/ep/admin/pne-license-manager/';
+}
+
+function _getTemplateData(data) {
+ var licenseInfo = licensing.getLicense();
+ data.licenseInfo = licenseInfo;
+ data.isUnlicensed = !licenseInfo;
+ data.isEvaluation = licensing.isEvaluation();
+ data.isExpired = licensing.isExpired();
+ data.isTooOld = licensing.isVersionTooOld();
+ data.errorMessage = (getSession().errorMessage || null);
+ data.runningVersionString = pne_utils.getVersionString();
+ data.licenseVersionString = licensing.getVersionString();
+ return data;
+}
+
+function render_main_get() {
+ licensing.reloadLicense();
+ var licenseInfo = licensing.getLicense();
+ if (!licenseInfo || licensing.isExpired()) {
+ response.redirect(getPath()+'edit');
+ }
+
+ pro_admin_control.renderAdminPage('pne-license-manager',
+ _getTemplateData({edit: false}));
+}
+
+function render_edit_get() {
+ licensing.reloadLicense();
+
+ if (request.params.btn) { response.redirect(request.path); }
+
+ var licenseInfo = licensing.getLicense();
+ var oldData = getSession().oldLicenseData;
+ if (!oldData) {
+ oldData = {};
+ if (licenseInfo) {
+ oldData.orgName = licenseInfo.organizationName;
+ oldData.personName = licenseInfo.personName;
+ }
+ }
+
+ pro_admin_control.renderAdminPage('pne-license-manager',
+ _getTemplateData({edit: true, oldData: oldData}));
+
+ delete getSession().errorMessage;
+}
+
+function render_edit_post() {
+ pne_utils.enableTrackingAgain();
+
+ function _trim(s) {
+ if (!s) { return ''; }
+ return stringutils.trim(s);
+ }
+ function _clean(s) {
+ s = s.replace(/\W/g, '');
+ s = s.replace(/\+/g, '');
+ return s;
+ }
+
+ if (request.params.cancel) {
+ delete getSession().oldLicenseData;
+ response.redirect(getPath());
+ }
+
+ var personName = _trim(request.params.personName);
+ var orgName = _trim(request.params.orgName);
+ var licenseString = _clean(request.params.licenseString);
+
+ getSession().oldLicenseData = {
+ personName: personName, orgName: orgName, licenseString: licenseString};
+
+ var key = [personName,orgName,licenseString].join(":");
+ println("validating key [ "+key+" ]");
+
+ if (!licensing.isValidKey(key)) {
+ getSession().errorMessage = "Invalid License Key";
+ response.redirect(request.path);
+ }
+
+ // valid key. write to disk.
+ var writeSuccess = false;
+ try {
+ println("writing key file: ./data/license.key");
+ writeRealFile("./data/license.key", key);
+ writeSuccess = true;
+ } catch (ex) {
+ println("exception: "+ex);
+ getSession().errorMessage = "Failed to write key to disk. (Do you have permission to write ./data/license.key ?).";
+ }
+ response.redirect(getPath());
+}
+
+
diff --git a/trunk/etherpad/src/etherpad/control/pro/admin/pro_admin_control.js b/trunk/etherpad/src/etherpad/control/pro/admin/pro_admin_control.js
new file mode 100644
index 0000000..f9ce179
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/control/pro/admin/pro_admin_control.js
@@ -0,0 +1,283 @@
+/**
+ * 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.
+ */
+
+import("stringutils");
+import("funhtml.*");
+import("dispatch.{Dispatcher,DirMatcher,forward}");
+
+import("etherpad.licensing");
+import("etherpad.control.admincontrol");
+import("etherpad.control.pro.admin.license_manager_control");
+import("etherpad.control.pro.admin.account_manager_control");
+import("etherpad.control.pro.admin.pro_config_control");
+import("etherpad.control.pro.admin.team_billing_control");
+
+import("etherpad.pad.padutils");
+
+import("etherpad.admin.shell");
+import("etherpad.sessions");
+import("etherpad.sessions.getSession");
+
+import("etherpad.pne.pne_utils");
+import("etherpad.pro.pro_accounts");
+import("etherpad.utils.*");
+
+//----------------------------------------------------------------
+
+var _pathPrefix = '/ep/admin/';
+
+var _PRO = 1;
+var _PNE_ONLY = 2;
+var _ONDEMAND_ONLY = 3;
+
+function _getLeftnavItems() {
+ var nav = [
+ _PRO, [
+ [_PRO, null, "Admin"],
+ [_PNE_ONLY, "pne-dashboard", "Server Dashboard"],
+ [_PNE_ONLY, "pne-license-manager/", "Manage License"],
+ [_PRO, "account-manager/", "Manage Accounts"],
+ [_PRO, "recover-padtext", "Recover Pad Text"],
+ [_PRO, null, "Configuration"],
+ [_PRO, [[_PNE_ONLY, "pne-config", "Private Server Configuration"],
+ [_PRO, "pro-config", "Application Configuration"]]],
+ [_PNE_ONLY, null, "Documentation"],
+ [_PNE_ONLY, "/ep/pne-manual/", "Administrator's Manual"],
+ ]
+ ];
+ return nav;
+}
+
+function renderAdminLeftNav() {
+ function _make(x) {
+ if ((x[0] == _PNE_ONLY) && !pne_utils.isPNE()) {
+ return null;
+ }
+ if ((x[0] == _ONDEMAND_ONLY) && pne_utils.isPNE()) {
+ return null;
+ }
+
+ if (x[1] instanceof Array) {
+ return _makelist(x[1]);
+ } else {
+ return _makeitem(x);
+ }
+ }
+ var selected;
+ function _makeitem(x) {
+ if (x[1]) {
+ var p = x[1];
+ if (x[1].charAt(0) != '/') {
+ p = _pathPrefix+p;
+ }
+ var li = LI(A({href: p}, x[2]));
+ if (stringutils.startsWith(request.path, p)) {
+ // select the longest prefix match.
+ if (! selected || p.length > selected.path.length) {
+ selected = {path: p, li: li};
+ }
+ }
+ return li;
+ } else {
+ return LI(DIV({className: 'leftnav-title'}, x[2]));
+ }
+ }
+ function _makelist(x) {
+ var ul = UL();
+ x.forEach(function(y) {
+ var t = _make(y);
+ if (t) { ul.push(t); }
+ });
+ return ul;
+ }
+ var d = DIV(_make(_getLeftnavItems()));
+ if (selected) {
+ selected.li.attribs.className = "selected";
+ }
+ // leftnav looks stupid when it's not very tall.
+ for (var i = 0; i < 10; i++) { d.push(BR()); }
+ return d;
+}
+
+function renderAdminPage(p, data) {
+ appjet.requestCache.proTopNavSelection = 'admin';
+ function getAdminContent() {
+ if (typeof(p) == 'function') {
+ return p();
+ } else {
+ return renderTemplateAsString('pro/admin/'+p+'.ejs', data);
+ }
+ }
+ renderFramed('pro/admin/admin-template.ejs', {
+ getAdminContent: getAdminContent,
+ renderAdminLeftNav: renderAdminLeftNav,
+ validLicense: pne_utils.isServerLicensed(),
+ });
+}
+
+//----------------------------------------------------------------
+
+function onRequest() {
+ var disp = new Dispatcher();
+ disp.addLocations([
+ [DirMatcher(license_manager_control.getPath()), forward(license_manager_control)],
+ [DirMatcher('/ep/admin/account-manager/'), forward(account_manager_control)],
+ [DirMatcher('/ep/admin/pro-config/'), forward(pro_config_control)],
+ [DirMatcher('/ep/admin/billing/'), forward(team_billing_control)],
+ ]);
+
+ if (disp.dispatch()) {
+ return true;
+ }
+
+ // request will be handled by this module.
+ pro_accounts.requireAdminAccount();
+}
+
+function render_main() {
+// renderAdminPage('admin');
+ response.redirect('/ep/admin/account-manager/')
+}
+
+function render_pne_dashboard() {
+ renderAdminPage('pne-dashboard', {
+ renderUptime: admincontrol.renderServerUptime,
+ renderResponseCodes: admincontrol.renderResponseCodes,
+ renderPadConnections: admincontrol.renderPadConnections,
+ renderTransportStats: admincontrol.renderCometStats,
+ todayActiveUsers: licensing.getActiveUserCount(),
+ userQuota: licensing.getActiveUserQuota()
+ });
+}
+
+var _documentedServerOptions = [
+ 'listen',
+ 'listenSecure',
+ 'transportUseWildcardSubdomains',
+ 'sslKeyStore',
+ 'sslKeyPassword',
+ 'etherpad.soffice',
+ 'etherpad.adminPass',
+ 'etherpad.SQL_JDBC_DRIVER',
+ 'etherpad.SQL_JDBC_URL',
+ 'etherpad.SQL_USERNAME',
+ 'etherpad.SQL_PASSWORD',
+ 'smtpServer',
+ 'smtpUser',
+ 'smtpPass',
+ 'configFile',
+ 'etherpad.licenseKey',
+ 'verbose'
+];
+
+function render_pne_config_get() {
+ renderAdminPage('pne-config', {
+ propKeys: _documentedServerOptions,
+ appjetConfig: appjet.config
+ });
+}
+
+function render_pne_advanced_get() {
+ response.redirect("/ep/admin/shell");
+}
+
+function render_shell_get() {
+ if (!(pne_utils.isPNE() || sessions.isAnEtherpadAdmin())) {
+ return false;
+ }
+ appjet.requestCache.proTopNavSelection = 'admin';
+ renderAdminPage('pne-shell', {
+ oldCmd: getSession().pneAdminShellCmd,
+ result: getSession().pneAdminShellResult,
+ elapsedMs: getSession().pneAdminShellElapsed
+ });
+ delete getSession().pneAdminShellResult;
+ delete getSession().pneAdminShellElapsed;
+}
+
+function render_shell_post() {
+ if (!(pne_utils.isPNE() || sessions.isAnEtherpadAdmin())) {
+ return false;
+ }
+ var cmd = request.params.cmd;
+ var start = +(new Date);
+ getSession().pneAdminShellCmd = cmd;
+ getSession().pneAdminShellResult = shell.getResult(cmd);
+ getSession().pneAdminShellElapsed = +(new Date) - start;
+ response.redirect(request.path);
+}
+
+function render_recover_padtext_get() {
+ function getNumRevisions(localPadId) {
+ return padutils.accessPadLocal(localPadId, function(pad) {
+ if (!pad.exists()) { return null; }
+ return 1+pad.getHeadRevisionNumber();
+ });
+ }
+ function getPadText(localPadId, revNum) {
+ return padutils.accessPadLocal(localPadId, function(pad) {
+ if (!pad.exists()) { return null; }
+ return pad.getRevisionText(revNum);
+ });
+ }
+
+ var localPadId = request.params.localPadId;
+ var revNum = request.params.revNum;
+
+ var d = DIV({style: "font-size: .8em;"});
+
+ d.push(FORM({action: request.path, method: "get"},
+ P({style: "margin-top: 0;"}, LABEL("Pad ID: "),
+ INPUT({type: "text", name: "localPadId", value: localPadId || ""}),
+ INPUT({type: "submit", value: "Submit"}))));
+
+ var showPadHelp = false;
+ var revisions = null;
+
+ if (!localPadId) {
+ showPadHelp = true;
+ } else {
+ revisions = getNumRevisions(localPadId);
+ if (!revisions) {
+ d.push(P("Pad not found: "+localPadId));
+ } else {
+ d.push(P(B(localPadId), " has ", revisions, " revisions."));
+ d.push(P("Enter a revision number (0-"+revisions+") to recover the pad text for that revision:"));
+ d.push(FORM({action: request.path, method: "get"},
+ P(LABEL("Revision number:"),
+ INPUT({type: "hidden", name: "localPadId", value: localPadId}),
+ INPUT({type: "text", name: "revNum", value: revNum || (revisions - 1)}),
+ INPUT({type: "submit", value: "Submit"}))));
+ }
+ }
+
+ if (showPadHelp) {
+ d.push(P({style: "font-size: 1em; color: #555;"},
+ 'The pad ID is the same as the URL to the pad, without the leading "/".',
+ ' For example, if the pad lives at http://pad.spline.inf.fu-berlin.de/foobar,',
+ ' then the pad ID is "foobar" (without the quotes).'))
+ }
+
+ if (revisions && revNum && (revNum < revisions)) {
+ var padText = getPadText(localPadId, revNum);
+ d.push(P(B("Pad text for ["+localPadId+"] revision #"+revNum)));
+ d.push(DIV({style: "font-family: monospace; border: 1px solid #ccc; background: #ffe; padding: 1em;"}, padText));
+ }
+
+ renderAdminPage(function() { return d; });
+}
+
+
diff --git a/trunk/etherpad/src/etherpad/control/pro/admin/pro_config_control.js b/trunk/etherpad/src/etherpad/control/pro/admin/pro_config_control.js
new file mode 100644
index 0000000..b03da45
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/control/pro/admin/pro_config_control.js
@@ -0,0 +1,54 @@
+/**
+ * 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.
+ */
+
+import("funhtml.*");
+
+import("etherpad.sessions.getSession");
+import("etherpad.control.pro.admin.pro_admin_control");
+import("etherpad.pro.pro_config");
+
+function _renderTopDiv(mid, htmlId) {
+ var m = getSession()[mid];
+ if (m) {
+ delete getSession()[mid];
+ return DIV({id: htmlId}, m);
+ } else {
+ return '';
+ }
+}
+
+function _messageDiv() {
+ return _renderTopDiv('proConfigMessage', 'pro-config-message');
+}
+
+function render_main_get() {
+ pro_config.reloadConfig();
+ var config = pro_config.getConfig();
+ pro_admin_control.renderAdminPage('pro-config', {
+ config: config,
+ messageDiv: _messageDiv
+ });
+}
+
+function render_main_post() {
+ pro_config.setConfigVal('siteName', request.params.siteName);
+ pro_config.setConfigVal('alwaysHttps', !!request.params.alwaysHttps);
+ pro_config.setConfigVal('defaultPadText', request.params.defaultPadText);
+ getSession().proConfigMessage = "New settings applied.";
+ response.redirect(request.path);
+}
+
+
diff --git a/trunk/etherpad/src/etherpad/control/pro/admin/team_billing_control.js b/trunk/etherpad/src/etherpad/control/pro/admin/team_billing_control.js
new file mode 100644
index 0000000..5be6a0e
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/control/pro/admin/team_billing_control.js
@@ -0,0 +1,447 @@
+/**
+ * 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.
+ */
+
+import("dateutils");
+import("email.sendEmail");
+import("fastJSON");
+import("funhtml.*");
+import("jsutils.*");
+import("sqlbase.sqlcommon.inTransaction");
+import("stringutils.*");
+
+import("etherpad.billing.billing");
+import("etherpad.billing.fields");
+import("etherpad.billing.team_billing");
+import("etherpad.control.pro.admin.pro_admin_control");
+import("etherpad.globals");
+import("etherpad.helpers");
+import("etherpad.pro.domains");
+import("etherpad.pro.pro_accounts");
+import("etherpad.pro.pro_utils");
+import("etherpad.sessions");
+import("etherpad.store.checkout");
+import("etherpad.utils.*");
+
+import("static.js.billing_shared.{billing=>billingJS}");
+
+var billingButtonName = "Confirm"
+
+function _cart() {
+ var s = sessions.getSession();
+ if (! s.proBillingCart) {
+ s.proBillingCart = {};
+ }
+ return s.proBillingCart;
+}
+
+function _billingForm() {
+ return renderTemplateAsString('store/eepnet-checkout/billing-info.ejs', {
+ cart: _cart(),
+ billingButtonName: billingButtonName,
+ billingFinalPhrase: "",
+ helpers: helpers,
+ errorIfInvalid: _errorIfInvalid,
+ billing: billingJS,
+ obfuscateCC: checkout.obfuscateCC,
+ dollars: checkout.dollars,
+ countryList: fields.countryList,
+ usaStateList: fields.usaStateList,
+ getFullSuperdomainHost: pro_utils.getFullSuperdomainHost,
+ showCouponCode: true,
+ });
+}
+
+function _plural(num) {
+ return (num == 1 ? "" : "s");
+}
+
+function _billingSummary(domainId, subscription) {
+ var paymentInfo = team_billing.getRecurringBillingInfo(domainId);
+ if (! paymentInfo) {
+ return;
+ }
+ var latestInvoice = team_billing.getLatestPaidInvoice(subscription.id);
+ var usersSoFar = team_billing.getMaxUsers(domainId);
+ var costSoFar = team_billing.calculateSubscriptionCost(usersSoFar, subscription.coupon);
+
+ var lastPaymentString =
+ (latestInvoice ?
+ "US $"+checkout.dollars(billing.centsToDollars(latestInvoice.amt))+
+ " ("+latestInvoice.users+" account"+_plural(latestInvoice.users)+")"+
+ ", on "+checkout.formatDate(latestInvoice.time) :
+ "None");
+
+ var coupon = false;
+ if (subscription.coupon) {
+ println("has a coupon: "+subscription.coupon);
+ var cval = team_billing.getCouponValue(subscription.coupon);
+ coupon = [];
+ if (cval.freeUsers) {
+ coupon.push(cval.freeUsers+" free user"+(cval.freeUsers == 1 ? "" : "s"));
+ }
+ if (cval.pctDiscount) {
+ coupon.push(cval.pctDiscount+"% savings");
+ }
+ coupon = coupon.join(", ");
+ }
+
+ return {
+ fullName: paymentInfo.fullname,
+ paymentSummary:
+ paymentInfo.paymentsummary +
+ (paymentInfo.expiration ?
+ ", expires "+checkout.formatExpiration(paymentInfo.expiration) :
+ ""),
+ lastPayment: lastPaymentString,
+ nextPayment: checkout.formatDate(subscription.paidThrough),
+ maxUsers: usersSoFar,
+ estimatedPayment: "US $"+checkout.dollars(costSoFar),
+ coupon: coupon
+ }
+}
+
+function _statusMessage() {
+ if (_cart().statusMessage) {
+ return toHTML(P({style: "color: green;"}, _cart().statusMessage));
+ } else {
+ return '';
+ }
+}
+
+function renderMainPage(doEdit) {
+ var cart = _cart();
+ var domainId = domains.getRequestDomainId();
+ var subscription = team_billing.getSubscriptionForCustomer(domainId);
+ var pendingInvoice = team_billing.getLatestPendingInvoice(domainId)
+ var usersSoFar = team_billing.getMaxUsers(domainId);
+ var costSoFar = team_billing.calculateSubscriptionCost(usersSoFar, subscription && subscription.coupon);
+
+ checkout.guessBillingNames(cart, pro_accounts.getSessionProAccount().fullName);
+ if (! cart.billingReferralCode) {
+ if (subscription && subscription.coupon) {
+ cart.billingReferralCode = subscription.coupon;
+ }
+ }
+
+ var summary = _billingSummary(domainId, subscription);
+ if (! summary) {
+ doEdit = true;
+ }
+
+ pro_admin_control.renderAdminPage('manage-billing', {
+ billingForm: _billingForm,
+ doEdit: doEdit,
+ paymentInfo: summary,
+ getFullSuperdomainHost: pro_utils.getFullSuperdomainHost,
+ firstCharge: checkout.formatDate(subscription ? subscription.paidThrough : dateutils.nextMonth(new Date)),
+ billingButtonName: billingButtonName,
+ errorDiv: _errorDiv,
+ showBackButton: (summary != undefined),
+ statusMessage: _statusMessage,
+ isBehind: (subscription ? subscription.paidThrough < Date.now() - 86400*1000 : false),
+ amountDue: "US $"+checkout.dollars(billing.centsToDollars(pendingInvoice ? pendingInvoice.amt : costSoFar*100)),
+ cart: _cart()
+ });
+
+ delete _cart().errorId;
+ delete _cart().errorMsg;
+ delete _cart().statusMessage;
+}
+
+function render_main() {
+ renderMainPage(false);
+}
+
+function render_edit() {
+ renderMainPage(true);
+}
+
+function _errorDiv() {
+ var m = _cart().errorMsg;
+ if (m) {
+ return DIV({className: 'errormsg', id: 'errormsg'}, m);
+ } else {
+ return '';
+ }
+}
+
+function _validationError(id, errorMessage) {
+ var cart = _cart();
+ cart.errorMsg = errorMessage;
+ cart.errorId = {};
+ if (id instanceof Array) {
+ id.forEach(function(k) {
+ cart.errorId[k] = true;
+ });
+ } else {
+ cart.errorId[id] = true;
+ }
+ response.redirect('/ep/admin/billing/edit');
+}
+
+function _errorIfInvalid(id) {
+ var cart = _cart();
+ if (cart.errorId && cart.errorId[id]) {
+ return 'error';
+ } else {
+ return '';
+ }
+}
+
+function paypalNotifyUrl() {
+ return request.scheme+"://"+pro_utils.getFullSuperdomainHost()+"/ep/store/paypalnotify";
+}
+
+function _paymentSummary(payInfo) {
+ return payInfo.cardType + " ending in " + payInfo.cardNumber.substr(-4);
+}
+
+function _expiration(payInfo) {
+ return payInfo.cardExpiration;
+}
+
+function _attemptAuthorization(success_f) {
+ var cart = _cart();
+ var domain = domains.getRequestDomainRecord();
+ var domainId = domain.id;
+ var domainName = domain.subDomain;
+ var payInfo = checkout.generatePayInfo(cart);
+ var proAccount = pro_accounts.getSessionProAccount();
+ var fullName = cart.billingFirstName+" "+cart.billingLastName;
+ var email = proAccount.email;
+
+ // PCI rules require that we not store the CVV longer than necessary to complete the transaction
+ var savedCvv = payInfo.cardCvv;
+ delete payInfo.cardCvv;
+ checkout.writeToEncryptedLog(fastJSON.stringify({date: String(new Date()), domain: domain, payInfo: payInfo}));
+ payInfo.cardCvv = savedCvv;
+
+ var result = billing.authorizePurchase(payInfo, paypalNotifyUrl());
+ if (result.status == 'success') {
+ billing.log({type: 'new-subscription',
+ name: fullName,
+ domainId: domainId,
+ domainName: domainName});
+ success_f(result);
+ } else if (result.status == 'pending') {
+ _validationError('', "Your authorization is pending. When it clears, your account will be activated. "+
+ "You may choose to pay by different means now, or wait until your authorization clears.");
+ } else if (result.status == 'failure') {
+ var paypalResult = result.debug;
+ billing.log({'type': 'FATAL', value: "Direct purchase failed on paypal.", cart: cart, paypal: paypalResult});
+ checkout.validateErrorFields(_validationError, "There seems to be an error in your billing information."+
+ " Please verify and correct your ",
+ result.errorField.userErrors);
+ checkout.validateErrorFields(_validationError, "The bank declined your billing information. Please try a different ",
+ result.errorField.permanentErrors);
+ _validationError('', "A temporary error has prevented processing of your payment. Please try again later.");
+ } else {
+ billing.log({'type': 'FATAL', value: "Unknown error: "+result.status+" - debug: "+result.debug});
+ sendEmail('support@pad.spline.inf.fu-berlin.de', 'urgent@pad.spline.inf.fu-berlin.de', 'UNKNOWN ERROR WARNING!', {},
+ "Hey,\n\nThis is a billing system error. Some unknown error occurred. "+
+ "This shouldn't ever happen. Probably good to let J.D. know. <grin>\n\n"+
+ fastJSON.stringify(cart));
+ _validationError('', "An unknown error occurred. We're looking into it!")
+ }
+}
+
+function _processNewSubscription() {
+ _attemptAuthorization(function(result) {
+ var domain = domains.getRequestDomainRecord();
+ var domainId = domain.id;
+ var domainName = domain.subDomain;
+
+ var cart = _cart();
+ var payInfo = checkout.generatePayInfo(cart);
+ var proAccount = pro_accounts.getSessionProAccount();
+ var fullName = cart.billingFirstName+" "+cart.billingLastName;
+ var email = proAccount.email;
+
+ inTransaction(function() {
+
+ var subscriptionId = team_billing.createSubscription(domainId, cart.billingReferralCode);
+
+ team_billing.setRecurringBillingInfo(
+ domainId,
+ fullName,
+ email,
+ _paymentSummary(payInfo),
+ _expiration(payInfo),
+ result.purchaseInfo.paypalId);
+ });
+
+ if (globals.isProduction()) {
+ sendEmail('sales@pad.spline.inf.fu-berlin.de', 'sales@pad.spline.inf.fu-berlin.de', "EtherPad: New paid pro account for "+fullName, {},
+ "This is an automatic notification.\n\n"+fullName+" ("+email+") successfully set up "+
+ "a billing profile for domain: "+domainName+".");
+ }
+ });
+}
+
+function _updateExistingSubscription(subscription) {
+ var cart = _cart();
+
+ _attemptAuthorization(function(result) {
+ inTransaction(function() {
+ var cart = _cart();
+ var domain = domains.getRequestDomainId();
+ var payInfo = checkout.generatePayInfo(cart);
+ var proAccount = pro_accounts.getSessionProAccount();
+ var fullName = cart.billingFirstName+" "+cart.billingLastName;
+ var email = proAccount.email;
+
+ var subscriptionId = subscription.id;
+
+ team_billing.setRecurringBillingInfo(
+ domain,
+ fullName,
+ email,
+ _paymentSummary(payInfo),
+ _expiration(payInfo),
+ result.purchaseInfo.paypalId);
+ });
+ });
+
+ if (subscription.paidThrough < new Date) {
+ // if they're behind, do the purchase!
+ if (team_billing.processSubscription(subscription)) {
+ cart.statusMessage = "Your payment was successful, and your account is now up to date! You will receive a receipt by email."
+ } else {
+ cart.statusMessage = "Your payment failed; you will receive further instructions by email.";
+ }
+ }
+}
+
+function _processBillingInfo() {
+ var cart = _cart();
+ var domain = domains.getRequestDomainId();
+
+ var subscription = team_billing.getSubscriptionForCustomer(domain);
+ if (! subscription) {
+ _processNewSubscription();
+ response.redirect('/ep/admin/billing/');
+ } else {
+ team_billing.updateSubscriptionCouponCode(subscription.id, cart.billingReferralCode);
+ if (cart.billingCCNumber.length > 0) {
+ _updateExistingSubscription(subscription);
+ }
+ response.redirect('/ep/admin/billing')
+ }
+}
+
+function _processPaypalPurchase() {
+ var domain = domains.getRequestDomainId();
+ billing.log({type: "paypal-attempt",
+ domain: domain,
+ message: "Someone tried to use paypal to pay for on-demand."+
+ " They got an error message. If this happens a lot, we should implement paypal."})
+ java.lang.Thread.sleep(5000);
+ _validationError('billingPurchaseType', "There was an error contacting PayPal. Please try another payment type.")
+}
+
+function _processInvoicePurchase() {
+ var output = [
+ "Name: "+cart.billingFirstName+" "+cart.billingLastName,
+ "\nAddress: ",
+ cart.billingAddressLine1+(cart.billingAddressLine2.length > 0 ? "\n"+cart.billingAddressLine2 : ""),
+ cart.billingCity + ", " + (cart.billingState.length > 0 ? cart.billingState : cart.billingProvince),
+ cart.billingZipCode.length > 0 ? cart.billingZipCode : cart.billingPostalCode,
+ cart.billingCountry,
+ "\nEmail: ",
+ pro_accounts.getSessionProAccount().email
+ ].join("\n");
+ var recipient = (globals.isProduction() ? 'sales@pad.spline.inf.fu-berlin.de' : 'jd@appjet.com');
+ sendEmail(
+ recipient,
+ 'sales@pad.spline.inf.fu-berlin.de',
+ 'Invoice payment request - '+pro_utils.getProRequestSubdomain(),
+ {},
+ "Hi there,\n\nA pro user tried to pay by invoice. Their information follows."+
+ "\n\nThanks!\n\n"+output);
+ _validationError('', "Your information has been sent to our sales department; a salesperson will contact you shortly regarding your invoice request.")
+}
+
+function render_apply() {
+ var cart = _cart();
+ eachProperty(request.params, function(k, v) {
+ if (startsWith(k, "billing")) {
+ if (k == "billingCCNumber" && v.charAt(0) == 'X') { return; }
+ cart[k] = toHTML(v);
+ }
+ });
+
+ if (! request.params.backbutton) {
+ var allPaymentFields = ["billingCCNumber", "billingExpirationMonth", "billingExpirationYear", "billingCSC", "billingAddressLine1", "billingAddressLine2", "billingCity", "billingState", "billingZipCode", "billingProvince", "billingPostalCode"];
+ var allBlank = true;
+ allPaymentFields.forEach(function(field) { if (cart[field].length > 0) { allBlank = false; }});
+ if (! allBlank) {
+ checkout.validateBillingCart(_validationError, cart);
+ }
+ } else {
+ response.redirect("/ep/admin/billing/");
+ }
+
+ var couponCode = cart.billingReferralCode;
+
+ if (couponCode.length != 0 && (couponCode.length != 8 || ! team_billing.getCouponValue(couponCode))) {
+ _validationError('billingReferralCode', 'Invalid referral code entered. Please verify your code and try again.');
+ }
+
+ if (cart.billingPurchaseType == 'paypal') {
+ _processPaypalPurchase();
+ } else if (cart.billingPurchaseType == 'invoice') {
+ _processInvoicePurchase();
+ }
+
+ _processBillingInfo();
+}
+
+function handlePaypalNotify() {
+ // XXX: handle delayed paypal authorization
+}
+
+function render_invoices() {
+ if (request.params.id) {
+ var purchaseId = team_billing.getSubscriptionForCustomer(domains.getRequestDomainId()).id;
+ var invoice = billing.getInvoice(request.params.id);
+ if (invoice.purchase != purchaseId) {
+ response.redirect(request.path);
+ }
+
+ var transaction;
+ var adjustments = billing.getAdjustments(invoice.id);
+ if (adjustments.length == 1) {
+ transaction = billing.getTransaction(adjustments[0].transaction);
+ }
+
+ pro_admin_control.renderAdminPage('single-invoice', {
+ formatDate: checkout.formatDate,
+ dollars: checkout.dollars,
+ centsToDollars: billing.centsToDollars,
+ invoice: invoice,
+ transaction: transaction
+ });
+ } else {
+ var invoices = team_billing.getAllInvoices(domains.getRequestDomainId());
+
+ pro_admin_control.renderAdminPage('billing-invoices', {
+ invoices: invoices,
+ formatDate: checkout.formatDate,
+ dollars: checkout.dollars,
+ centsToDollars: billing.centsToDollars
+ });
+ }
+} \ No newline at end of file
diff --git a/trunk/etherpad/src/etherpad/control/pro/pro_main_control.js b/trunk/etherpad/src/etherpad/control/pro/pro_main_control.js
new file mode 100644
index 0000000..b4e3bc4
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/control/pro/pro_main_control.js
@@ -0,0 +1,150 @@
+/**
+ * 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.
+ */
+
+import("stringutils");
+import("dispatch.{Dispatcher,DirMatcher,forward}");
+import("funhtml.*");
+import("cache_utils.syncedWithCache");
+
+import("etherpad.helpers");
+import("etherpad.utils.*");
+import("etherpad.sessions.getSession");
+import("etherpad.licensing");
+import("etherpad.pne.pne_utils");
+import("etherpad.pro.pro_pad_db");
+import("etherpad.pro.domains");
+import("etherpad.pro.pro_accounts");
+import("etherpad.pro.pro_accounts.getSessionProAccount");
+import("etherpad.pro.pro_padlist");
+
+import("etherpad.control.pro.account_control");
+import("etherpad.control.pro.pro_padlist_control");
+import("etherpad.control.pro.admin.pro_admin_control");
+import("etherpad.control.pro.admin.account_manager_control");
+
+import("etherpad.pad.activepads");
+import("etherpad.pad.model");
+
+
+function onRequest() {
+ var disp = new Dispatcher();
+ disp.addLocations([
+ [DirMatcher('/ep/account/'), forward(account_control)],
+ [DirMatcher('/ep/admin/'), forward(pro_admin_control)],
+ [DirMatcher('/ep/padlist/'), forward(pro_padlist_control)],
+ ]);
+ return disp.dispatch();
+}
+
+function render_main() {
+ if (request.path == '/ep/') {
+ response.redirect('/');
+ }
+
+ // recent pad list
+ var livePads = pro_pad_db.listLiveDomainPads();
+ var recentPads = pro_pad_db.listAllDomainPads();
+
+ var renderLivePads = function() {
+ return pro_padlist.renderPadList(livePads, ['title', 'connectedUsers'], 10);
+ }
+
+ var renderRecentPads = function() {
+ return pro_padlist.renderPadList(recentPads, ['title'], 10);
+ };
+
+ var r = domains.getRequestDomainRecord();
+
+ renderFramed('pro/pro_home.ejs', {
+ isEvaluation: licensing.isEvaluation(),
+ account: getSessionProAccount(),
+ isPNE: pne_utils.isPNE(),
+ pneVersion: pne_utils.getVersionString(),
+ livePads: livePads,
+ recentPads: recentPads,
+ renderRecentPads: renderRecentPads,
+ renderLivePads: renderLivePads,
+ orgName: r.orgName
+ });
+ return true;
+}
+
+function render_finish_activation_get() {
+ if (!isActivationAllowed()) {
+ response.redirect('/');
+ }
+
+ var accountList = pro_accounts.listAllDomainAccounts();
+ if (accountList.length > 1) {
+ response.redirect('/');
+ }
+ if (accountList.length == 0) {
+ throw Error("accountList.length should never be 0.");
+ }
+
+ var acct = accountList[0];
+ var tempPass = stringutils.randomString(10);
+ pro_accounts.setTempPassword(acct, tempPass);
+ account_manager_control.sendWelcomeEmail(acct, tempPass);
+
+ var domainId = domains.getRequestDomainId();
+
+ syncedWithCache('pro-activations', function(c) {
+ delete c[domainId];
+ });
+
+ renderNoticeString(
+ DIV({style: "font-size: 16pt; border: 1px solid green; background: #eeffee; margin: 2em 4em; padding: 1em;"},
+ P("Success! You will receive an email shortly with instructions."),
+ DIV({style: "display: none;", id: "reference"}, acct.id, ":", tempPass)));
+}
+
+function isActivationAllowed() {
+ if (request.path != '/ep/finish-activation') {
+ return false;
+ }
+ var allowed = false;
+ var domainId = domains.getRequestDomainId();
+ return syncedWithCache('pro-activations', function(c) {
+ if (c[domainId]) {
+ return true;
+ }
+ return false;
+ });
+}
+
+function render_payment_required_get() {
+ // Users get to this page when there is a problem with billing:
+ // possibilities:
+ // * they try to create a new account but they have not entered
+ // payment information
+ //
+ // * their credit card lapses and any pro request fails.
+ //
+ // * others?
+
+ var message = getSession().billingProblem || "A payment is required to proceed.";
+ var adminList = pro_accounts.listAllDomainAdmins();
+
+ renderFramed("pro/pro-payment-required.ejs", {
+ message: message,
+ isAdmin: pro_accounts.isAdminSignedIn(),
+ adminList: adminList
+ });
+}
+
+
+
diff --git a/trunk/etherpad/src/etherpad/control/pro/pro_padlist_control.js b/trunk/etherpad/src/etherpad/control/pro/pro_padlist_control.js
new file mode 100644
index 0000000..9a90c67
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/control/pro/pro_padlist_control.js
@@ -0,0 +1,200 @@
+/**
+ * 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.
+ */
+
+import("funhtml.*");
+import("jsutils.*");
+import("stringutils");
+
+import("etherpad.sessions.getSession");
+import("etherpad.utils.*");
+import("etherpad.helpers");
+import("etherpad.pad.exporthtml");
+import("etherpad.pad.padutils");
+import("etherpad.pro.pro_padmeta");
+import("etherpad.pro.pro_pad_db");
+import("etherpad.pro.domains");
+import("etherpad.pro.pro_accounts");
+import("etherpad.pro.pro_padlist");
+
+jimport("java.lang.System.out.println");
+
+function onRequest(name) {
+ if (name == "all_pads.zip") {
+ render_all_pads_zip_get();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+function _getBaseUrl() { return "/ep/padlist/"; }
+
+function _renderPadNav() {
+ var d = DIV({id: "padlist-nav"});
+ var ul = UL();
+ var items = [
+ ['allpads', 'all-pads', "All Pads"],
+ ['mypads', 'my-pads', "My Pads"],
+ ['archivedpads', 'archived-pads', "Archived Pads"]
+ ];
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ var cn = "";
+ if (request.path.split("/").slice(-1)[0] == item[1]) {
+ cn = "selected";
+ }
+ ul.push(LI(A({id: "nav-"+item[1], href: _getBaseUrl()+item[1], className: cn}, item[2])));
+ }
+ ul.push(html(helpers.clearFloats()));
+ d.push(ul);
+ d.push(FORM({id: "newpadform", method: "get", action: "/ep/pad/newpad"},
+ INPUT({type: "submit", value: "New Pad"})));
+ d.push(html(helpers.clearFloats()));
+ return d;
+}
+
+function _renderPage(name, data) {
+ getSession().latestPadlistView = request.path + "?" + request.query;
+ var r = domains.getRequestDomainRecord();
+ appjet.requestCache.proTopNavSelection = 'padlist';
+ data.renderPadNav = _renderPadNav;
+ data.orgName = r.orgName;
+ data.renderNotice = function() {
+ var m = getSession().padlistMessage;
+ if (m) {
+ delete getSession().padlistMessage;
+ return DIV({className: "padlist-notice"}, m);
+ } else {
+ return "";
+ }
+ };
+
+ renderFramed("pro/padlist/"+name+".ejs", data);
+}
+
+function _renderListPage(padList, showingDesc, columns) {
+ _renderPage("pro-padlist", {
+ padList: padList,
+ renderPadList: function() {
+ return pro_padlist.renderPadList(padList, columns);
+ },
+ renderShowingDesc: function(count) {
+ return DIV({id: "showing-desc"},
+ "Showing "+showingDesc+" ("+count+").");
+ },
+ isAdmin: pro_accounts.isAdminSignedIn()
+ });
+}
+
+function render_main() {
+ if (!getSession().latestPadlistView) {
+ getSession().latestPadlistView = "/ep/padlist/all-pads";
+ }
+ response.redirect(getSession().latestPadlistView);
+}
+
+function render_all_pads_get() {
+ _renderListPage(
+ pro_pad_db.listAllDomainPads(),
+ "all pads",
+ ['secure', 'title', 'lastEditedDate', 'editors', 'actions']);
+}
+
+function render_all_pads_zip_get() {
+ if (! pro_accounts.isAdminSignedIn()) {
+ response.redirect(_getBaseUrl()+"all-pads");
+ }
+ var bytes = new java.io.ByteArrayOutputStream();
+ var zos = new java.util.zip.ZipOutputStream(bytes);
+
+ var pads = pro_pad_db.listAllDomainPads();
+ pads.forEach(function(pad) {
+ var padHtml;
+ var title;
+ padutils.accessPadLocal(pad.localPadId, function(p) {
+ title = padutils.getProDisplayTitle(pad.localPadId, pad.title);
+ padHtml = exporthtml.getPadHTML(p);
+ }, "r");
+
+ title = title.replace(/[^\w\s]/g, "-") + ".html";
+ zos.putNextEntry(new java.util.zip.ZipEntry(title));
+ var padBytes = (new java.lang.String(renderTemplateAsString('pad/exporthtml.ejs', {
+ content: padHtml,
+ pre: false
+ }))).getBytes("UTF-8");
+
+ zos.write(padBytes, 0, padBytes.length);
+ zos.closeEntry();
+ });
+ zos.close();
+ response.setContentType("application/zip");
+ response.writeBytes(bytes.toByteArray());
+}
+
+function render_my_pads_get() {
+ _renderListPage(
+ pro_pad_db.listMyPads(),
+ "pads created by me",
+ ['secure', 'title', 'lastEditedDate', 'editors', 'actions']);
+}
+
+function render_archived_pads_get() {
+ helpers.addClientVars({
+ showingArchivedPads: true
+ });
+ _renderListPage(
+ pro_pad_db.listArchivedPads(),
+ "archived pads",
+ ['secure', 'title', 'lastEditedDate', 'actions']);
+}
+
+function render_edited_by_get() {
+ var editorId = request.params.editorId;
+ var editorName = pro_accounts.getFullNameById(editorId);
+ _renderListPage(
+ pro_pad_db.listPadsByEditor(editorId),
+ "pads edited by "+editorName,
+ ['secure', 'title', 'lastEditedDate', 'editors', 'actions']);
+}
+
+function render_delete_post() {
+ var localPadId = request.params.padIdToDelete;
+
+ pro_padmeta.accessProPadLocal(localPadId, function(propad) {
+ propad.markDeleted();
+ getSession().padlistMessage = 'Pad "'+propad.getDisplayTitle()+'" has been deleted.';
+ });
+
+ response.redirect(request.params.returnPath);
+}
+
+function render_toggle_archive_post() {
+ var localPadId = request.params.padIdToToggleArchive;
+
+ pro_padmeta.accessProPadLocal(localPadId, function(propad) {
+ if (propad.isArchived()) {
+ propad.unmarkArchived();
+ getSession().padlistMessage = 'Pad "'+propad.getDisplayTitle()+'" has been un-archived.';
+ } else {
+ propad.markArchived();
+ getSession().padlistMessage = 'Pad "'+propad.getDisplayTitle()+'" has been archived. You can view archived pads by clicking on the "Archived" tab at the top of the pad list.';
+ }
+ });
+
+ response.redirect(request.params.returnPath);
+}
+
+