UNPKG

apostrophe

Version:

Apostrophe is a user-friendly content management system. You'll need more than this core module. See apostrophenow.org to get started.

217 lines (202 loc) • 7.38 kB
var _ = require('lodash'); var async = require('async'); /** * workflow * @augments Augments the apos object with methods which provide * workflow-related services. See also pages.js. * @see pages */ module.exports = function(self) { // Given an array of pages retrieved from mongodb, return a // new array that represents what would be retrieved if all // outstanding drafts were published. The ._draft property is // set to true so you can distinguish the pages based on // outstanding drafts from those that are current. self.workflowGetDrafts = function(pages) { return _.map(pages, function(page) { if (!page.draft) { return page; } var draft = {}; _.extend(draft, page); _.extend(draft, page.draft); delete draft.draft; // So we can distinguish drafts from live pages with no outstanding edits draft._draft = true; return draft; }); }; // Given an array of pages retrieved from mongodb, modify // each page object by removing any information about drafts. // This does not modify the database. self.workflowCleanPages = function(pages) { _.each(pages, function(page) { if (page.draft) { delete page.draft; } }); }; // Update the specified properties of the current draft of // the page in the database. If those properties do not exist on the page // object provided, they are unset rather than set. Also // adds ._draft = true to the passed page object as a reminder // that it contains drafts rather than final content. // // It's your responsibility not to call this with dangerous // property names in "props", like slug, path, level, _id, pagePermissions self.workflowUpdatePage = function(req, page, props, callback) { if (arguments.length === 3) { // for bc we tolerate a missing req on this method, // it is only used to set draftAuthoredById req = { user: { title: 'unknown', _id: 'unknown' } }; page = arguments[0]; props = arguments[1]; callback = arguments[2]; } // Flag what we have in memory now as a draft, so it looks like // what we'd get back from workflowGetDrafts, and so that the // versionPage method records the draftiness page._draft = true; var work = {}; var $set = { draftAuthoredById: (req.user && req.user._id) || 'unknown' }; var $unset = {}; _.each(props, function(prop) { if (page[prop] !== undefined) { $set['draft.' + prop] = page[prop]; } else { $unset['draft.' + prop] = 1; } }); if (!_.isEmpty($set)) { work.$set = $set; } if (!_.isEmpty($unset)) { work.$unset = $unset; } if (_.isEmpty(work)) { return setImmediate(callback); } return self.pages.update({ _id: page._id }, work, callback); }; // Given a page object, update its live content to match // its draft. self.workflowApproveChanges = function(req, page, callback) { if (!page.draft) { return setImmediate(callback); } // TODO this won't know what to do if the draft change consisted // of deleting a property. We should move toward // true/false rather than true/nonexistent for boolean properties _.extend(page, page.draft); // Not a draft anymore delete page.draft; delete page.submitDraft; return async.series({ beforePutPage: function(callback) { // Invoke this so that code intended for use // without workflow can still react when a // change is finally approved return self.beforePutPage(req, page, callback); }, update: function(callback) { return self.pages.update({ _id: page._id }, page, callback); }, afterPutPage: function(callback) { // Invoke this so that code intended for use // without workflow can still react when a // change is finally approved return self.afterPutPage(req, page, callback); } }, callback); }; self.workflowRequestApproval = function(req, page, callback) { // Make someone with suitable privileges aware that // this page is ready for approval page.submitDraft = new Date(); return self.pages.update({ _id: page._id }, { $set: { submitDraft: page.submitDraft, draftSubmittedBy: req.user && req.user.title } }, callback); }; // Called with req.extras by renderPage // to shut off the _edit flag for any editable pages when we are looking // at the public view. Returns true if any of the pages would have been // editable. Also sets _contribute to true on those pages. self.workflowPreventEditInPublicMode = function(possiblePages) { var contribute = false; _.each(possiblePages, function(value, key) { if (value && value._edit) { contribute = true; value._contribute = true; value._edit = false; } }); return contribute; }; self.app.post('/apos/workflow-mode', function(req, res) { if (!self.options.workflow) { // Politely ignore return res.send({ status: 'ok' }); } req.session.workflowMode = self.sanitizeSelect(req.body.mode, [ 'draft', 'public' ], 'public'); return res.send({ status: 'ok' }); }); self.app.post('/apos/workflow-approve-changes', function(req, res) { var submitted = 0; var published = 0; if (!self.options.workflow) { // Politely ignore return res.send({ status: 'ok' }); } var slugs = self.sanitizeStrings(req.body.slugs); // A page is the fundamental unit of publication, so // reduce the page:areaname slugs to just the unique // page slugs slugs = _.uniq(_.map(slugs, function(slug) { return slug.replace(/\:\w+$/, ''); })); var page; return async.eachSeries(slugs, function(slug, mainCallback) { return async.series({ getPage: function(callback) { // Get the raw mongo document, with the draft property return self.pages.findOne({ slug: slug }, function(err, _page) { if (err) { return callback(err); } if (!_page) { // Not there any more return mainCallback(null); } if (!_page.draft) { // Has not been modified return mainCallback(null); } if (!self.permissions.can(req, 'edit-page', _page)) { // Not ours. It's a pain for the browser to detect which // are ours, so just gracefully ignore it return mainCallback(null); } page = _page; return callback(null); }); }, putPage: function(callback) { if (((!(self.options.workflow && self.options.workflow.forPublishers)) || (page.draftAuthoredById !== req.user._id)) && self.permissions.can(req, 'publish-page', page)) { published++; return self.workflowApproveChanges(req, page, callback); } else { submitted++; return self.workflowRequestApproval(req, page, callback); } } }, mainCallback); }, function(err) { if (err) { return res.send({ status: 'error' }); } return res.send({ status: 'ok', submitted: submitted, published: published }); }); }); };