]> source.dussan.org Git - nextcloud-server.git/commitdiff
Add workflowengine
authorMorris Jobke <hey@morrisjobke.de>
Tue, 26 Jul 2016 09:16:34 +0000 (11:16 +0200)
committerMorris Jobke <hey@morrisjobke.de>
Tue, 26 Jul 2016 09:16:34 +0000 (11:16 +0200)
17 files changed:
.gitignore
apps/workflowengine/appinfo/app.php [new file with mode: 0644]
apps/workflowengine/appinfo/database.xml [new file with mode: 0644]
apps/workflowengine/appinfo/info.xml [new file with mode: 0644]
apps/workflowengine/appinfo/routes.php [new file with mode: 0644]
apps/workflowengine/css/admin.css [new file with mode: 0644]
apps/workflowengine/js/admin.js [new file with mode: 0644]
apps/workflowengine/js/usergroupmembershipplugin.js [new file with mode: 0644]
apps/workflowengine/lib/AppInfo/Application.php [new file with mode: 0644]
apps/workflowengine/lib/Check/UserGroupMembership.php [new file with mode: 0644]
apps/workflowengine/lib/Controller/FlowOperations.php [new file with mode: 0644]
apps/workflowengine/lib/Manager.php [new file with mode: 0644]
core/shipped.json
lib/public/WorkflowEngine/ICheck.php [new file with mode: 0644]
lib/public/WorkflowEngine/IManager.php [new file with mode: 0644]
lib/public/WorkflowEngine/RegisterCheckEvent.php [new file with mode: 0644]
tests/lib/App/ManagerTest.php

index f60dda513e4bbf197a03cf88aaa53f4aa21d8e14..63a34beb978d4827fbdd9ac0cee151d3e91afb6a 100644 (file)
@@ -27,6 +27,7 @@
 !/apps/admin_audit
 !/apps/updatenotification
 !/apps/theming
+!/apps/workflowengine
 /apps/files_external/3rdparty/irodsphp/PHPUnitTest
 /apps/files_external/3rdparty/irodsphp/web
 /apps/files_external/3rdparty/irodsphp/prods/test
diff --git a/apps/workflowengine/appinfo/app.php b/apps/workflowengine/appinfo/app.php
new file mode 100644 (file)
index 0000000..f6f22ce
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+$application = new \OCA\WorkflowEngine\AppInfo\Application();
+$application->registerHooksAndListeners();
diff --git a/apps/workflowengine/appinfo/database.xml b/apps/workflowengine/appinfo/database.xml
new file mode 100644 (file)
index 0000000..b67a41f
--- /dev/null
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<database>
+       <name>*dbname*</name>
+       <create>true</create>
+       <overwrite>false</overwrite>
+       <charset>utf8</charset>
+
+       <table>
+               <name>*dbprefix*flow_checks</name>
+               <declaration>
+                       <field>
+                               <name>id</name>
+                               <type>integer</type>
+                               <default>0</default>
+                               <notnull>true</notnull>
+                               <autoincrement>1</autoincrement>
+                               <length>4</length>
+                       </field>
+
+                       <field>
+                               <name>class</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>256</length>
+                       </field>
+                       <field>
+                               <name>operator</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>16</length>
+                       </field>
+                       <field>
+                               <name>value</name>
+                               <type>clob</type>
+                               <notnull>false</notnull>
+                       </field>
+                       <field>
+                               <name>hash</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>32</length>
+                       </field>
+
+                       <index>
+                               <name>flow_unique_hash</name>
+                               <unique>true</unique>
+                               <field>
+                                       <name>hash</name>
+                               </field>
+                       </index>
+               </declaration>
+       </table>
+
+       <table>
+               <name>*dbprefix*flow_operations</name>
+               <declaration>
+                       <field>
+                               <name>id</name>
+                               <type>integer</type>
+                               <default>0</default>
+                               <notnull>true</notnull>
+                               <autoincrement>1</autoincrement>
+                               <length>4</length>
+                       </field>
+
+                       <field>
+                               <name>class</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>256</length>
+                       </field>
+                       <field>
+                               <name>name</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>256</length>
+                       </field>
+                       <field>
+                               <name>checks</name>
+                               <type>clob</type>
+                               <notnull>false</notnull>
+                       </field>
+                       <field>
+                               <name>operation</name>
+                               <type>clob</type>
+                               <notnull>false</notnull>
+                       </field>
+               </declaration>
+       </table>
+</database>
diff --git a/apps/workflowengine/appinfo/info.xml b/apps/workflowengine/appinfo/info.xml
new file mode 100644 (file)
index 0000000..066589c
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<info>
+       <id>workflowengine</id>
+       <name>Files Workflow Engine</name>
+       <description></description>
+       <licence>AGPL</licence>
+       <author>Morris Jobke</author>
+       <version>1.0.0</version>
+       <namespace>WorkflowEngine</namespace>
+
+       <category>other</category>
+       <website>https://github.com/nextcloud/server</website>
+       <bugs>https://github.com/nextcloud/server/issues</bugs>
+       <repository type="git">https://github.com/nextcloud/server.git</repository>
+
+       <types>
+               <filesystem/>
+       </types>
+
+       <dependencies>
+               <owncloud min-version="9.2" max-version="9.2" />
+       </dependencies>
+</info>
diff --git a/apps/workflowengine/appinfo/routes.php b/apps/workflowengine/appinfo/routes.php
new file mode 100644 (file)
index 0000000..69478b1
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+return [
+       'routes' => [
+               ['name' => 'flowOperations#getChecks', 'url' => '/checks', 'verb' => 'GET'], // TODO rm and do via js?
+               ['name' => 'flowOperations#getOperations', 'url' => '/operations', 'verb' => 'GET'],
+               ['name' => 'flowOperations#addOperation', 'url' => '/operations', 'verb' => 'POST'],
+               ['name' => 'flowOperations#updateOperation', 'url' => '/operations/{id}', 'verb' => 'PUT'],
+               ['name' => 'flowOperations#deleteOperation', 'url' => '/operations/{id}', 'verb' => 'DELETE'],
+       ]
+];
diff --git a/apps/workflowengine/css/admin.css b/apps/workflowengine/css/admin.css
new file mode 100644 (file)
index 0000000..73ac448
--- /dev/null
@@ -0,0 +1,43 @@
+.workflowengine .operation {
+       padding: 5px;
+       border-bottom: #eee 1px solid;
+       border-left: rgba(0,0,0,0) 1px solid;
+}
+.workflowengine .operation.modified {
+       border-left: rgb(255, 94, 32) 1px solid;
+}
+.workflowengine .operation button {
+       margin-bottom: 0;
+}
+.workflowengine .operation span.info {
+       padding: 7px;
+       color: #eee;
+}
+.workflowengine .rules .operation:nth-last-child(2) {
+       margin-bottom: 5px;
+}
+
+.workflowengine .pull-right {
+       float: right
+}
+
+.workflowengine .operation .msg {
+       border-radius: 3px;
+       margin: 3px 3px 3px 0;
+       padding: 5px;
+       transition: opacity .5s;
+}
+
+.workflowengine .operation .button-delete,
+.workflowengine .operation .button-delete-check {
+       opacity: 0.5;
+       padding: 7px;
+}
+.workflowengine .operation .button-delete:hover,
+.workflowengine .operation .button-delete:focus,
+.workflowengine .operation .button-delete-check:hover,
+.workflowengine .operation .button-delete-check:focus {
+       opacity: 1;
+       cursor: pointer;
+}
+
diff --git a/apps/workflowengine/js/admin.js b/apps/workflowengine/js/admin.js
new file mode 100644 (file)
index 0000000..a29ad82
--- /dev/null
@@ -0,0 +1,372 @@
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function() {
+       Handlebars.registerHelper('selectItem', function(currentValue, itemValue) {
+               if(currentValue === itemValue) {
+                       return 'selected=selected';
+               }
+
+               return "";
+       });
+
+       Handlebars.registerHelper('getOperators', function(classname) {
+               return OCA.WorkflowEngine.availableChecks
+                       .getOperatorsByClassName(classname);
+       });
+
+       OCA.WorkflowEngine = OCA.WorkflowEngine || {};
+
+       /**
+        * 888b     d888               888          888
+        * 8888b   d8888               888          888
+        * 88888b.d88888               888          888
+        * 888Y88888P888  .d88b.   .d88888  .d88b.  888 .d8888b
+        * 888 Y888P 888 d88""88b d88" 888 d8P  Y8b 888 88K
+        * 888  Y8P  888 888  888 888  888 88888888 888 "Y8888b.
+        * 888   "   888 Y88..88P Y88b 888 Y8b.     888      X88
+        * 888       888  "Y88P"   "Y88888  "Y8888  888  88888P'
+        */
+
+       /**
+        * @class OCA.WorkflowEngine.Operation
+        */
+       OCA.WorkflowEngine.Operation =
+               OC.Backbone.Model.extend({
+                       defaults: {
+                               'class': 'OCA\\WorkflowEngine\\Operation',
+                               'name': '',
+                               'checks': [],
+                               'operation': ''
+                       }
+               });
+
+       /**
+        * @class OCA.WorkflowEngine.AvailableCheck
+        */
+       OCA.WorkflowEngine.AvailableCheck =
+               OC.Backbone.Model.extend({});
+
+       /**
+        *  .d8888b.           888 888                   888    d8b
+        * d88P  Y88b          888 888                   888    Y8P
+        * 888    888          888 888                   888
+        * 888         .d88b.  888 888  .d88b.   .d8888b 888888 888  .d88b.  88888b.  .d8888b
+        * 888        d88""88b 888 888 d8P  Y8b d88P"    888    888 d88""88b 888 "88b 88K
+        * 888    888 888  888 888 888 88888888 888      888    888 888  888 888  888 "Y8888b.
+        * Y88b  d88P Y88..88P 888 888 Y8b.     Y88b.    Y88b.  888 Y88..88P 888  888      X88
+        *  "Y8888P"   "Y88P"  888 888  "Y8888   "Y8888P  "Y888 888  "Y88P"  888  888  88888P'
+        */
+
+       /**
+        * @class OCA.WorkflowEngine.OperationsCollection
+        *
+        * collection for all configurated operations
+        */
+       OCA.WorkflowEngine.OperationsCollection =
+               OC.Backbone.Collection.extend({
+                       model: OCA.WorkflowEngine.Operation,
+                       url: OC.generateUrl('apps/workflowengine/operations')
+               });
+
+       /**
+        * @class OCA.WorkflowEngine.AvailableChecksCollection
+        *
+        * collection for all available checks
+        */
+       OCA.WorkflowEngine.AvailableChecksCollection =
+               OC.Backbone.Collection.extend({
+                       model: OCA.WorkflowEngine.AvailableCheck,
+                       url: OC.generateUrl('apps/workflowengine/checks'),
+                       getOperatorsByClassName: function(classname) {
+                               return OCA.WorkflowEngine.availableChecks
+                                       .findWhere({'class': classname})
+                                       .get('operators');
+                       }
+               });
+
+       /**
+        * 888     888 d8b
+        * 888     888 Y8P
+        * 888     888
+        * Y88b   d88P 888  .d88b.  888  888  888 .d8888b
+        *  Y88b d88P  888 d8P  Y8b 888  888  888 88K
+        *   Y88o88P   888 88888888 888  888  888 "Y8888b.
+        *    Y888P    888 Y8b.     Y88b 888 d88P      X88
+        *     Y8P     888  "Y8888   "Y8888888P"   88888P'
+        */
+
+       /**
+        * @class OCA.WorkflowEngine.TemplateView
+        *
+        * a generic template that handles the Handlebars template compile step
+        * in a method called "template()"
+        */
+       OCA.WorkflowEngine.TemplateView =
+               OC.Backbone.View.extend({
+                       _template: null,
+                       template: function(vars) {
+                               if (!this._template) {
+                                       this._template = Handlebars.compile($(this.templateId).html());
+                               }
+                               return this._template(vars);
+                       }
+               });
+
+       /**
+        * @class OCA.WorkflowEngine.OperationView
+        *
+        * this creates the view for a single operation
+        */
+       OCA.WorkflowEngine.OperationView =
+               OCA.WorkflowEngine.TemplateView.extend({
+                       templateId: '#operation-template',
+                       events: {
+                               'change .check-class': 'checkChanged',
+                               'change .check-operator': 'checkChanged',
+                               'change .check-value': 'checkChanged',
+                               'change .operation-name': 'operationChanged',
+                               'click .button-reset': 'reset',
+                               'click .button-save': 'save',
+                               'click .button-add': 'add',
+                               'click .button-delete': 'delete',
+                               'click .button-delete-check': 'deleteCheck'
+                       },
+                       originalModel: null,
+                       hasChanged: false,
+                       message: '',
+                       errorMessage: '',
+                       saving: false,
+                       plugins: [],
+                       initialize: function() {
+                               // this creates a new copy of the object to definitely have a new reference and being able to reset the model
+                               this.originalModel = JSON.parse(JSON.stringify(this.model));
+                               this.model.on('change', function(){
+                                       console.log('model changed');
+                                       this.hasChanged = true;
+                                       this.render();
+                               }, this);
+
+                               if (this.model.get('id') === undefined) {
+                                       this.hasChanged = true;
+                               }
+
+                               this.plugins = OC.Plugins.getPlugins('OCA.WorkflowEngine.CheckPlugins');
+                               _.each(this.plugins, function(plugin) {
+                                       if (_.isFunction(plugin.initialize)) {
+                                               plugin.initialize();
+                                       }
+                               });
+                       },
+                       delete: function() {
+                               this.model.destroy();
+                               this.remove();
+                       },
+                       reset: function() {
+                               this.hasChanged = false;
+                               // silent is need to not trigger the change event which resets the hasChanged attribute
+                               this.model.set(this.originalModel, {silent: true});
+                               this.render();
+                       },
+                       save: function() {
+                               var success = function(model, response, options) {
+                                       this.saving = false;
+                                       this.originalModel = JSON.parse(JSON.stringify(this.model));
+
+                                       this.message = t('workflowengine', 'Successfully saved');
+                                       this.errorMessage = '';
+                                       this.render();
+                               };
+                               var error = function(model, response, options) {
+                                       this.saving = false;
+                                       this.hasChanged = true;
+
+                                       this.message = t('workflowengine', 'Saving failed:');
+                                       this.errorMessage = response.responseText;
+                                       this.render();
+                               };
+                               this.hasChanged = false;
+                               this.saving = true;
+                               this.render();
+                               this.model.save(null, {success: success, error: error, context: this});
+                       },
+                       add: function() {
+                               var checks = _.clone(this.model.get('checks')),
+                                       classname = OCA.WorkflowEngine.availableChecks.at(0).get('class'),
+                                       operators = OCA.WorkflowEngine.availableChecks
+                                               .getOperatorsByClassName(classname);
+
+                               checks.push({
+                                       'class': classname,
+                                       'operator': operators[0],
+                                       'value': ''
+                               });
+                               this.model.set({'checks': checks});
+                       },
+                       checkChanged: function(event) {
+                               var value = event.target.value,
+                                       id = $(event.target.parentElement).data('id'),
+                                       // this creates a new copy of the object to definitely have a new reference
+                                       checks = JSON.parse(JSON.stringify(this.model.get('checks'))),
+                                       key = null;
+
+                               for (var i = 0; i < event.target.classList.length; i++) {
+                                       var className = event.target.classList[i];
+                                       if (className.substr(0, 'check-'.length) === 'check-') {
+                                               key = className.substr('check-'.length);
+                                               break;
+                                       }
+                               }
+
+                               if (key === null) {
+                                       console.warn('checkChanged triggered but element doesn\'t have any "check-" class');
+                                       return;
+                               }
+
+                               if (!_.has(checks[id], key)) {
+                                       console.warn('key "' + key + '" is not available in check', check);
+                                       return;
+                               }
+
+                               checks[id][key] = value;
+                               // if the class is changed most likely also the operators have changed
+                               // with this we set the operator to the first possible operator
+                               if (key === 'class') {
+                                       var operators = OCA.WorkflowEngine.availableChecks
+                                               .getOperatorsByClassName(value);
+                                       checks[id]['operator'] = operators[0];
+                               }
+                               // model change will trigger render
+                               this.model.set({'checks': checks});
+                       },
+                       deleteCheck: function() {
+                               console.log(arguments);
+                               var id = $(event.target.parentElement).data('id'),
+                                       checks = JSON.parse(JSON.stringify(this.model.get('checks')));
+
+                               // splice removes 1 element at index `id`
+                               checks.splice(id, 1);
+                               // model change will trigger render
+                               this.model.set({'checks': checks});
+                       },
+                       operationChanged: function(event) {
+                               var value = event.target.value,
+                                       key = null;
+
+                               for (var i = 0; i < event.target.classList.length; i++) {
+                                       var className = event.target.classList[i];
+                                       if (className.substr(0, 'operation-'.length) === 'operation-') {
+                                               key = className.substr('operation-'.length);
+                                               break;
+                                       }
+                               }
+
+                               if (key === null) {
+                                       console.warn('operationChanged triggered but element doesn\'t have any "operation-" class');
+                                       return;
+                               }
+
+                               if (key !== 'name') {
+                                       console.warn('key "' + key + '" is no valid attribute');
+                                       return;
+                               }
+
+                               // model change will trigger render
+                               this.model.set(key, value);
+                       },
+                       render: function() {
+                               this.$el.html(this.template({
+                                       operation: this.model.toJSON(),
+                                       classes: OCA.WorkflowEngine.availableChecks.toJSON(),
+                                       hasChanged: this.hasChanged,
+                                       message: this.message,
+                                       errorMessage: this.errorMessage,
+                                       saving: this.saving
+                               }));
+
+                               var checks = this.model.get('checks');
+                               _.each(this.$el.find('.check'), function(element){
+                                       var $element = $(element),
+                                               id = $element.data('id'),
+                                               check = checks[id],
+                                               valueElement = $element.find('.check-value').first();
+
+                                       _.each(this.plugins, function(plugin) {
+                                               if (_.isFunction(plugin.render)) {
+                                                       plugin.render(valueElement, check['class'], check['value']);
+                                               }
+                                       });
+                               }, this);
+
+                               if (this.message !== '') {
+                                       // hide success messages after some time
+                                       _.delay(function(elements){
+                                               $(elements).css('opacity', 0);
+                                       }, 7000, this.$el.find('.msg.success'));
+                                       this.message = '';
+                               }
+
+                       }
+               });
+
+       /**
+        * @class OCA.WorkflowEngine.OperationsView
+        *
+        * this creates the view for configured operations
+        */
+       OCA.WorkflowEngine.OperationsView =
+               OCA.WorkflowEngine.TemplateView.extend({
+                       templateId: '#operations-template',
+                       events: {
+                               'click .button-add-operation': 'add'
+                       },
+                       initialize: function() {
+                               this._initialize('OCA\\WorkflowEngine\\Operation');
+                       },
+                       _initialize: function(classname) {
+                               var data = {};
+                               if (this.operationsClass !== null) {
+                                       data['class'] = this.operationsClass;
+                               }
+                               this.collection.fetch({data: {
+                                       'class': classname
+                               }});
+                               this.collection.once('sync', this.render, this);
+                       },
+                       add: function() {
+                               var operation = new OCA.WorkflowEngine.Operation();
+                               this.collection.add(operation);
+                               this.renderOperation(operation);
+                       },
+                       renderOperation: function(operation){
+                               console.log(operation);
+                               var subView = new OCA.WorkflowEngine.OperationView({
+                                               model: operation
+                                       }),
+                                       operationsElement = this.$el.find('.operations');
+                               operationsElement.append(subView.$el);
+                               subView.render();
+                       },
+                       render: function() {
+                               this.$el.html(this.template());
+                               this.collection.each(this.renderOperation, this);
+                       }
+               });
+})();
diff --git a/apps/workflowengine/js/usergroupmembershipplugin.js b/apps/workflowengine/js/usergroupmembershipplugin.js
new file mode 100644 (file)
index 0000000..2a6068c
--- /dev/null
@@ -0,0 +1,85 @@
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function() {
+
+       OCA.WorkflowEngine = OCA.WorkflowEngine || {};
+       OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
+
+       OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin = {
+               render: function(element, classname, value) {
+                       if (classname !== 'OCA\\WorkflowEngine\\Check\\UserGroupMembership') {
+                               return;
+                       }
+
+                       $(element).css('width', '400px');
+
+                       $(element).select2({
+                               ajax: {
+                                       url: OC.generateUrl('settings/users/groups'),
+                                       dataType: 'json',
+                                       quietMillis: 100,
+                                       data: function (term) {
+                                               return {
+                                                       pattern: term, //search term
+                                                       filterGroups: true,
+                                                       sortGroups: 2 // by groupname
+                                               };
+                                       },
+                                       results: function (response) {
+                                               // TODO improve error case
+                                               if (response.data === undefined) {
+                                                       console.error('Failure happened', response);
+                                                       return;
+                                               }
+
+                                               var results = [];
+
+                                               // add admin groups
+                                               $.each(response.data.adminGroups, function(id, group) {
+                                                       results.push({ id: group.id });
+                                               });
+                                               // add groups
+                                               $.each(response.data.groups, function(id, group) {
+                                                       results.push({ id: group.id });
+                                               });
+
+                                               // TODO once limit and offset is implemented for groups we should paginate the search results
+                                               return {
+                                                       results: results,
+                                                       more: false
+                                               };
+                                       }
+                               },
+                               initSelection: function (element, callback) {
+                                       callback({id: element.val()});
+                               },
+                               formatResult: function (element) {
+                                       return '<span>' + escapeHTML(element.id) + '</span>';
+                               },
+                               formatSelection: function (element) {
+                                       return '<span title="'+escapeHTML(element.id)+'">'+escapeHTML(element.id)+'</span>';
+                               }
+                       });
+               }
+       };
+})();
+
+OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin);
diff --git a/apps/workflowengine/lib/AppInfo/Application.php b/apps/workflowengine/lib/AppInfo/Application.php
new file mode 100644 (file)
index 0000000..c196ecd
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\WorkflowEngine\AppInfo;
+
+use OCP\Util;
+use OCP\WorkflowEngine\RegisterCheckEvent;
+
+class Application extends \OCP\AppFramework\App {
+
+       public function __construct() {
+               parent::__construct('workflowengine');
+
+               $this->getContainer()->registerAlias('FlowOperationsController', 'OCA\WorkflowEngine\Controller\FlowOperations');
+       }
+
+       /**
+        * Register all hooks and listeners
+        */
+       public function registerHooksAndListeners() {
+               $dispatcher = $this->getContainer()->getServer()->getEventDispatcher();
+               $dispatcher->addListener(
+                       'OCP\WorkflowEngine\RegisterCheckEvent',
+                       function(RegisterCheckEvent $event) {
+                               $event->addCheck(
+                                       'OCA\WorkflowEngine\Check\UserGroupMembership',
+                                       'User group membership',
+                                       ['is', '!is']
+                               );
+                       },
+                       -100
+               );
+
+               $dispatcher->addListener(
+                       'OCP\WorkflowEngine::loadAdditionalSettingScripts',
+                       function() {
+                               Util::addStyle('workflowengine', 'admin');
+                               Util::addScript('workflowengine', 'admin');
+                               Util::addScript('workflowengine', 'usergroupmembershipplugin');
+                       },
+                       -100
+               );
+       }
+}
diff --git a/apps/workflowengine/lib/Check/UserGroupMembership.php b/apps/workflowengine/lib/Check/UserGroupMembership.php
new file mode 100644 (file)
index 0000000..f437dbf
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\WorkflowEngine\Check;
+
+
+use OCP\Files\Storage\IStorage;
+use OCP\IGroupManager;
+use OCP\IUser;
+use OCP\IUserSession;
+use OCP\WorkflowEngine\ICheck;
+
+class UserGroupMembership implements ICheck {
+
+       /** @var string */
+       protected $cachedUser;
+
+       /** @var string[] */
+       protected $cachedGroupMemberships;
+
+       /** @var IUserSession */
+       protected $userSession;
+
+       /** @var IGroupManager */
+       protected $groupManager;
+
+       /**
+        * @param IUserSession $userSession
+        * @param IGroupManager $groupManager
+        */
+       public function __construct(IUserSession $userSession, IGroupManager $groupManager) {
+               $this->userSession = $userSession;
+               $this->groupManager = $groupManager;
+       }
+
+       /**
+        * @param IStorage $storage
+        * @param string $path
+        */
+       public function setFileInfo(IStorage $storage, $path) {
+               // A different path doesn't change group memberships, so nothing to do here.
+       }
+
+       /**
+        * @param string $operator
+        * @param string $value
+        * @return bool
+        */
+       public function executeCheck($operator, $value) {
+               $user = $this->userSession->getUser();
+
+               if ($user instanceof IUser) {
+                       $groupIds = $this->getUserGroups($user);
+                       return ($operator === 'is') === in_array($value, $groupIds);
+               } else {
+                       return $operator !== 'is';
+               }
+       }
+
+
+       /**
+        * @param string $operator
+        * @param string $value
+        * @throws \UnexpectedValueException
+        */
+       public function validateCheck($operator, $value) {
+               if (!in_array($operator, ['is', '!is'])) {
+                       throw new \UnexpectedValueException('Invalid operator', 1);
+               }
+
+               if (!$this->groupManager->groupExists($value)) {
+                       throw new \UnexpectedValueException('Group does not exist', 2);
+               }
+       }
+
+       /**
+        * @param IUser $user
+        * @return string[]
+        */
+       protected function getUserGroups(IUser $user) {
+               $uid = $user->getUID();
+
+               if ($this->cachedUser !== $uid) {
+                       $this->cachedUser = $uid;
+                       $this->cachedGroupMemberships = $this->groupManager->getUserGroupIds($user);
+               }
+
+               return $this->cachedGroupMemberships;
+       }
+}
diff --git a/apps/workflowengine/lib/Controller/FlowOperations.php b/apps/workflowengine/lib/Controller/FlowOperations.php
new file mode 100644 (file)
index 0000000..e0836c7
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\WorkflowEngine\Controller;
+
+use OCA\WorkflowEngine\Manager;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\IRequest;
+use OCP\WorkflowEngine\RegisterCheckEvent;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+class FlowOperations extends Controller {
+
+       /** @var Manager */
+       protected $manager;
+
+       /** @var EventDispatcherInterface */
+       protected $dispatcher;
+
+       /**
+        * @param IRequest $request
+        * @param Manager $manager
+        * @param EventDispatcherInterface $dispatcher
+        */
+       public function __construct(IRequest $request, Manager $manager, EventDispatcherInterface $dispatcher) {
+               parent::__construct('workflowengine', $request);
+               $this->manager = $manager;
+               $this->dispatcher = $dispatcher;
+       }
+
+       /**
+        * @NoCSRFRequired
+        *
+        * @return JSONResponse
+        */
+       public function getChecks() {
+               $event = new RegisterCheckEvent();
+               $this->dispatcher->dispatch('OCP\WorkflowEngine\RegisterCheckEvent', $event);
+
+               return new JSONResponse($event->getChecks());
+       }
+
+       /**
+        * @NoCSRFRequired
+        *
+        * @param string $class
+        * @return JSONResponse
+        */
+       public function getOperations($class) {
+               $operations = $this->manager->getOperations($class);
+
+               foreach ($operations as &$operation) {
+                       $operation = $this->prepareOperation($operation);
+               }
+
+               return new JSONResponse($operations);
+       }
+
+       /**
+        * @param string $class
+        * @param string $name
+        * @param array[] $checks
+        * @param string $operation
+        * @return JSONResponse The added element
+        */
+       public function addOperation($class, $name, $checks, $operation) {
+               try {
+                       $operation = $this->manager->addOperation($class, $name, $checks, $operation);
+                       $operation = $this->prepareOperation($operation);
+                       return new JSONResponse($operation);
+               } catch (\UnexpectedValueException $e) {
+                       return new JSONResponse($e->getMessage(), Http::STATUS_BAD_REQUEST);
+               }
+       }
+
+       /**
+        * @param int $id
+        * @param string $name
+        * @param array[] $checks
+        * @param string $operation
+        * @return JSONResponse The updated element
+        */
+       public function updateOperation($id, $name, $checks, $operation) {
+               try {
+                       $operation = $this->manager->updateOperation($id, $name, $checks, $operation);
+                       $operation = $this->prepareOperation($operation);
+                       return new JSONResponse($operation);
+               } catch (\UnexpectedValueException $e) {
+                       return new JSONResponse($e->getMessage(), Http::STATUS_BAD_REQUEST);
+               }
+       }
+
+       /**
+        * @param int $id
+        * @return JSONResponse
+        */
+       public function deleteOperation($id) {
+               $deleted = $this->manager->deleteOperation((int) $id);
+               return new JSONResponse($deleted);
+       }
+
+       /**
+        * @param array $operation
+        * @return array
+        */
+       protected function prepareOperation(array $operation) {
+               $checkIds = json_decode($operation['checks']);
+               $checks = $this->manager->getChecks($checkIds);
+
+               $operation['checks'] = [];
+               foreach ($checks as $check) {
+                       // Remove internal values
+                       unset($check['id']);
+                       unset($check['hash']);
+
+                       $operation['checks'][] = $check;
+               }
+
+               return $operation;
+       }
+}
diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php
new file mode 100644 (file)
index 0000000..98c34e8
--- /dev/null
@@ -0,0 +1,306 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\WorkflowEngine;
+
+
+use OCP\AppFramework\QueryException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Files\Storage\IStorage;
+use OCP\IDBConnection;
+use OCP\IServerContainer;
+use OCP\WorkflowEngine\ICheck;
+use OCP\WorkflowEngine\IManager;
+
+class Manager implements IManager {
+
+       /** @var IStorage */
+       protected $storage;
+
+       /** @var string */
+       protected $path;
+
+       /** @var array[] */
+       protected $operations = [];
+
+       /** @var array[] */
+       protected $checks = [];
+
+       /** @var IDBConnection */
+       protected $connection;
+
+       /** @var IServerContainer|\OC\Server */
+       protected $container;
+
+       /**
+        * @param IDBConnection $connection
+        * @param IServerContainer $container
+        */
+       public function __construct(IDBConnection $connection, IServerContainer $container) {
+               $this->connection = $connection;
+               $this->container = $container;
+       }
+
+       /**
+        * @inheritdoc
+        */
+       public function setFileInfo(IStorage $storage, $path) {
+               $this->storage = $storage;
+               $this->path = $path;
+       }
+
+       /**
+        * @inheritdoc
+        */
+       public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true) {
+               $operations = $this->getOperations($class);
+
+               $matches = [];
+               foreach ($operations as $operation) {
+                       $checkIds = json_decode($operation['checks'], true);
+                       $checks = $this->getChecks($checkIds);
+
+                       foreach ($checks as $check) {
+                               if (!$this->check($check)) {
+                                       // Check did not match, continue with the next operation
+                                       continue 2;
+                               }
+                       }
+
+                       if ($returnFirstMatchingOperationOnly) {
+                               return $operation;
+                       }
+                       $matches[] = $operation;
+               }
+
+               return $matches;
+       }
+
+       /**
+        * @param array $check
+        * @return bool
+        */
+       protected function check(array $check) {
+               try {
+                       $checkInstance = $this->container->query($check['class']);
+               } catch (QueryException $e) {
+                       // Check does not exist, assume it matches.
+                       return true;
+               }
+
+               if ($checkInstance instanceof ICheck) {
+                       $checkInstance->setFileInfo($this->storage, $this->path);
+                       return $checkInstance->executeCheck($check['operator'], $check['value']);
+               } else {
+                       // Check is invalid, assume it matches.
+                       return true;
+               }
+       }
+
+       /**
+        * @param string $class
+        * @return array[]
+        */
+       public function getOperations($class) {
+               if (isset($this->operations[$class])) {
+                       return $this->operations[$class];
+               }
+
+               $query = $this->connection->getQueryBuilder();
+
+               $query->select('*')
+                       ->from('flow_operations')
+                       ->where($query->expr()->eq('class', $query->createNamedParameter($class)));
+               $result = $query->execute();
+
+               $this->operations[$class] = [];
+               while ($row = $result->fetch()) {
+                       $this->operations[$class][] = $row;
+               }
+               $result->closeCursor();
+
+               return $this->operations[$class];
+       }
+
+       /**
+        * @param int $id
+        * @return array
+        * @throws \UnexpectedValueException
+        */
+       protected function getOperation($id) {
+               $query = $this->connection->getQueryBuilder();
+               $query->select('*')
+                       ->from('flow_operations')
+                       ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
+               $result = $query->execute();
+               $row = $result->fetch();
+               $result->closeCursor();
+
+               if ($row) {
+                       return $row;
+               }
+
+               throw new \UnexpectedValueException('Operation does not exist');
+       }
+
+       /**
+        * @param string $class
+        * @param string $name
+        * @param array[] $checks
+        * @param string $operation
+        * @return array The added operation
+        * @throws \UnexpectedValueException
+        */
+       public function addOperation($class, $name, array $checks, $operation) {
+               $checkIds = [];
+               foreach ($checks as $check) {
+                       $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
+               }
+
+               $query = $this->connection->getQueryBuilder();
+               $query->insert('flow_operations')
+                       ->values([
+                               'class' => $query->createNamedParameter($class),
+                               'name' => $query->createNamedParameter($name),
+                               'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
+                               'operation' => $query->createNamedParameter($operation),
+                       ]);
+               $query->execute();
+
+               $id = $query->getLastInsertId();
+               return $this->getOperation($id);
+       }
+
+       /**
+        * @param int $id
+        * @param string $name
+        * @param array[] $checks
+        * @param string $operation
+        * @return array The updated operation
+        * @throws \UnexpectedValueException
+        */
+       public function updateOperation($id, $name, array $checks, $operation) {
+               $checkIds = [];
+               foreach ($checks as $check) {
+                       $checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
+               }
+
+               $query = $this->connection->getQueryBuilder();
+               $query->update('flow_operations')
+                       ->set('name', $query->createNamedParameter($name))
+                       ->set('checks', $query->createNamedParameter(json_encode(array_unique($checkIds))))
+                       ->set('operation', $query->createNamedParameter($operation))
+                       ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
+               $query->execute();
+
+               return $this->getOperation($id);
+       }
+
+       /**
+        * @param int $id
+        * @return bool
+        * @throws \UnexpectedValueException
+        */
+       public function deleteOperation($id) {
+               $query = $this->connection->getQueryBuilder();
+               $query->delete('flow_operations')
+                       ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
+               return (bool) $query->execute();
+       }
+
+       /**
+        * @param int[] $checkIds
+        * @return array[]
+        */
+       public function getChecks(array $checkIds) {
+               $checkIds = array_map('intval', $checkIds);
+
+               $checks = [];
+               foreach ($checkIds as $i => $checkId) {
+                       if (isset($this->checks[$checkId])) {
+                               $checks[$checkId] = $this->checks[$checkId];
+                               unset($checkIds[$i]);
+                       }
+               }
+
+               if (empty($checkIds)) {
+                       return $checks;
+               }
+
+               $query = $this->connection->getQueryBuilder();
+               $query->select('*')
+                       ->from('flow_checks')
+                       ->where($query->expr()->in('id', $query->createNamedParameter($checkIds, IQueryBuilder::PARAM_INT_ARRAY)));
+               $result = $query->execute();
+
+               $checks = [];
+               while ($row = $result->fetch()) {
+                       $this->checks[(int) $row['id']] = $row;
+                       $checks[(int) $row['id']] = $row;
+               }
+               $result->closeCursor();
+
+               // TODO What if a check is missing? Should we throw?
+               // As long as we only allow AND-concatenation of checks, a missing check
+               // is like a matching check, so it evaluates to true and therefor blocks
+               // access. So better save than sorry.
+
+               return $checks;
+       }
+
+       /**
+        * @param string $class
+        * @param string $operator
+        * @param string $value
+        * @return int Check unique ID
+        * @throws \UnexpectedValueException
+        */
+       protected function addCheck($class, $operator, $value) {
+               /** @var ICheck $check */
+               $check = $this->container->query($class);
+               $check->validateCheck($operator, $value);
+
+               $hash = md5($class . '::' . $operator . '::' . $value);
+
+               $query = $this->connection->getQueryBuilder();
+               $query->select('id')
+                       ->from('flow_checks')
+                       ->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
+               $result = $query->execute();
+
+               if ($row = $result->fetch()) {
+                       $result->closeCursor();
+                       return (int) $row['id'];
+               }
+
+               $query = $this->connection->getQueryBuilder();
+               $query->insert('flow_checks')
+                       ->values([
+                               'class' => $query->createNamedParameter($class),
+                               'operator' => $query->createNamedParameter($operator),
+                               'value' => $query->createNamedParameter($value),
+                               'hash' => $query->createNamedParameter($hash),
+                       ]);
+               $query->execute();
+
+               return $query->getLastInsertId();
+       }
+}
index da48d14dc0b2fff07a8e91b8f355d2ea061c65b6..8d3056eb9083389b5c8478097abf2052609d7a34 100644 (file)
     "updatenotification",
     "user_external",
     "user_ldap",
-    "user_saml"
+    "user_saml",
+    "workflowengine"
   ],
   "alwaysEnabled": [
     "files",
     "dav",
-    "federatedfilesharing"
+    "federatedfilesharing",
+    "workflowengine"
   ]
 }
diff --git a/lib/public/WorkflowEngine/ICheck.php b/lib/public/WorkflowEngine/ICheck.php
new file mode 100644 (file)
index 0000000..7e3d86c
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\WorkflowEngine;
+
+
+use OCP\Files\Storage\IStorage;
+
+/**
+ * Interface ICheck
+ *
+ * @package OCP\WorkflowEngine
+ * @since 9.1
+ */
+interface ICheck {
+       /**
+        * @param IStorage $storage
+        * @param string $path
+        * @since 9.1
+        */
+       public function setFileInfo(IStorage $storage, $path);
+
+       /**
+        * @param string $operator
+        * @param string $value
+        * @return bool
+        * @since 9.1
+        */
+       public function executeCheck($operator, $value);
+
+       /**
+        * @param string $operator
+        * @param string $value
+        * @throws \UnexpectedValueException
+        * @since 9.1
+        */
+       public function validateCheck($operator, $value);
+}
diff --git a/lib/public/WorkflowEngine/IManager.php b/lib/public/WorkflowEngine/IManager.php
new file mode 100644 (file)
index 0000000..e53a06e
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\WorkflowEngine;
+
+
+use OCP\Files\Storage\IStorage;
+
+/**
+ * Interface IManager
+ *
+ * @package OCP\WorkflowEngine
+ * @since 9.1
+ */
+interface IManager {
+       /**
+        * @param IStorage $storage
+        * @param string $path
+        * @since 9.1
+        */
+       public function setFileInfo(IStorage $storage, $path);
+
+       /**
+        * @param string $class
+        * @param bool $returnFirstMatchingOperationOnly
+        * @return array
+        * @since 9.1
+        */
+       public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true);
+}
diff --git a/lib/public/WorkflowEngine/RegisterCheckEvent.php b/lib/public/WorkflowEngine/RegisterCheckEvent.php
new file mode 100644 (file)
index 0000000..e08aae5
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\WorkflowEngine;
+
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Class RegisterCheckEvent
+ *
+ * @package OCP\WorkflowEngine
+ * @since 9.1
+ */
+class RegisterCheckEvent extends Event {
+
+       /** @var array[] */
+       protected $checks = [];
+
+       /**
+        * @param string $class
+        * @param string $name
+        * @param string[] $operators
+        * @throws \OutOfBoundsException when the check class is already registered
+        * @throws \OutOfBoundsException when the provided information is invalid
+        * @since 9.1
+        */
+       public function addCheck($class, $name, array $operators) {
+               if (!is_string($class)) {
+                       throw new \OutOfBoundsException('Given class name is not a string');
+               }
+
+               if (isset($this->checks[$class])) {
+                       throw new \OutOfBoundsException('Duplicate check class "' . $class . '"');
+               }
+
+               if (!is_string($name)) {
+                       throw new \OutOfBoundsException('Given check name is not a string');
+               }
+
+               foreach ($operators as $operator) {
+                       if (!is_string($operator)) {
+                               throw new \OutOfBoundsException('At least one of the operators is not a string');
+                       }
+               }
+
+               $this->checks[$class] = [
+                       'class' => $class,
+                       'name' => $name,
+                       'operators' => $operators,
+               ];
+       }
+
+       /**
+        * @return array[]
+        * @since 9.1
+        */
+       public function getChecks() {
+               return array_values($this->checks);
+       }
+}
index 2d4ec4968b00ac12f647a06c6a0a16621dfd59db..80754413fc81e74b542b68eea02e1a1ac45c481f 100644 (file)
@@ -306,7 +306,7 @@ class ManagerTest extends TestCase {
                $this->appConfig->setValue('test1', 'enabled', 'yes');
                $this->appConfig->setValue('test2', 'enabled', 'no');
                $this->appConfig->setValue('test3', 'enabled', '["foo"]');
-               $this->assertEquals(['dav', 'federatedfilesharing', 'files', 'test1', 'test3'], $this->manager->getInstalledApps());
+               $this->assertEquals(['dav', 'federatedfilesharing', 'files', 'test1', 'test3', 'workflowengine'], $this->manager->getInstalledApps());
        }
 
        public function testGetAppsForUser() {
@@ -320,7 +320,7 @@ class ManagerTest extends TestCase {
                $this->appConfig->setValue('test2', 'enabled', 'no');
                $this->appConfig->setValue('test3', 'enabled', '["foo"]');
                $this->appConfig->setValue('test4', 'enabled', '["asd"]');
-               $this->assertEquals(['dav', 'federatedfilesharing', 'files', 'test1', 'test3'], $this->manager->getEnabledAppsForUser($user));
+               $this->assertEquals(['dav', 'federatedfilesharing', 'files', 'test1', 'test3', 'workflowengine'], $this->manager->getEnabledAppsForUser($user));
        }
 
        public function testGetAppsNeedingUpgrade() {
@@ -338,6 +338,7 @@ class ManagerTest extends TestCase {
                        'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'],
                        'test4' => ['id' => 'test4', 'version' => '3.0.0', 'requiremin' => '8.1.0'],
                        'testnoversion' => ['id' => 'testnoversion', 'requiremin' => '8.2.0'],
+                       'workflowengine' => ['id' => 'workflowengine'],
                ];
 
                $this->manager->expects($this->any())
@@ -378,6 +379,7 @@ class ManagerTest extends TestCase {
                        'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'],
                        'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'],
                        'testnoversion' => ['id' => 'testnoversion', 'requiremin' => '8.2.0'],
+                       'workflowengine' => ['id' => 'workflowengine'],
                ];
 
                $this->manager->expects($this->any())