diff --git a/appinfo/app.php b/appinfo/app.php index ca974b7..ee9bcb5 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -22,4 +22,3 @@ */ $app = new \OCA\WorkflowScript\AppInfo\Application(); -$app->registerHooksAndListeners(); diff --git a/appinfo/info.xml b/appinfo/info.xml index c7b9a0a..ebcf9d5 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -23,8 +23,4 @@ - - OCA\WorkflowScript\Settings\Admin - OCA\WorkflowScript\Settings\Section - diff --git a/js/admin.js b/js/admin.js index 6b0598d..541803a 100644 --- a/js/admin.js +++ b/js/admin.js @@ -19,79 +19,58 @@ */ (function() { - OCA.WorkflowScript = OCA.WorkflowScript || {}; - /** - * @class OCA.WorkflowScript.Operation - */ - OCA.WorkflowScript.Operation = - OCA.WorkflowEngine.Operation.extend({ - defaults: { - 'class': 'OCA\\WorkflowScript\\Operation', - 'name': '', - 'checks': [], - 'operation': '' + var Component = { + name: 'WorkflowScript', + render: function (createElement) { + var self = this + return createElement('div', { + style: { + width: '100%' + }, + }, [ + createElement('input', { + attrs: { + type: 'text' + }, + domProps: { + value: self.value, + required: 'true' + }, + style: { + width: '100%' + }, + on: { + input: function (event) { + self.$emit('input', event.target.value) + } + } + }), + createElement('a', { + attrs: { + href: self.link + }, + style: { + color: 'var(--color-text-maxcontrast)' + } + }, self.description) + ]) + }, + props: { + value: '' + }, + data: function () { + return { + description: t('workflow_script', 'Available placeholder variables are listed in the documentation') + '↗', + link: 'https://github.com/nextcloud/workflow_script#placeholders' } - }); - - /** - * @class OCA.WorkflowScript.OperationsCollection - * - * collection for all configured operations - */ - OCA.WorkflowScript.OperationsCollection = - OCA.WorkflowEngine.OperationsCollection.extend({ - model: OCA.WorkflowScript.Operation - }); - - /** - * @class OCA.WorkflowScript.OperationView - * - * this creates the view for a single operation - */ - OCA.WorkflowScript.OperationView = - OCA.WorkflowEngine.OperationView.extend({ - model: OCA.WorkflowScript.Operation, - render: function() { - var $el = OCA.WorkflowEngine.OperationView.prototype.render.apply(this); - $el.find('input.operation-operation') - .css('width', '400px') - .attr("placeholder", t('workflow_script', 'command to execute')) - } - }); + } + }; - /** - * @class OCA.WorkflowScript.OperationsView - * - * this creates the view for configured operations - */ - OCA.WorkflowScript.OperationsView = - OCA.WorkflowEngine.OperationsView.extend({ - initialize: function() { - OCA.WorkflowEngine.OperationsView.prototype.initialize.apply(this, [ - 'OCA\\WorkflowScript\\Operation' - ]); - }, - renderOperation: function(operation) { - var subView = new OCA.WorkflowScript.OperationView({ - model: operation - }); + OCA.WorkflowEngine.registerOperator({ + id: 'OCA\\WorkflowScript\\Operation', + operation: '', + options: Component + }); - OCA.WorkflowEngine.OperationsView.prototype.renderOperation.apply(this, [ - subView - ]); - } - }); })(); - - -$(document).ready(function() { - OC.SystemTags.collection.fetch({ - success: function() { - new OCA.WorkflowScript.OperationsView({ - el: '#workflow_script .rules', - collection: new OCA.WorkflowScript.OperationsCollection() - }); - } - }); -}); diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 9e6d842..357d05b 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -24,10 +24,8 @@ namespace OCA\WorkflowScript\AppInfo; use OCA\WorkflowScript\Operation; -use OCP\AppFramework\QueryException; -use OCP\Files\Folder; -use OCP\Files\Node; -use OCP\ILogger; +use OCP\WorkflowEngine\IManager; +use Symfony\Component\EventDispatcher\GenericEvent; class Application extends \OCP\AppFramework\App { @@ -36,45 +34,11 @@ class Application extends \OCP\AppFramework\App { */ public function __construct() { parent::__construct('workflow_script'); - } - - protected function handleEvent(Node $node, string $eventName, array $extra = []) { - // '', admin, 'files', 'path/to/file.txt' - list(,, $folder,) = explode('/', $node->getPath(), 4); - if($folder !== 'files' || $node instanceof Folder) { - return; - } - - try { - $operation = $this->getContainer()->query(Operation::class); - /** @var Operation $operation */ - $operation->considerScript($node, $eventName, $extra); - } catch (QueryException $e) { - $logger = $this->getContainer()->getServer()->getLogger(); - $logger->logException($e, ['app' => 'workflow_script', 'level' => ILogger::ERROR]); - } - } - - public function onCreate(Node $node) { - $this->handleEvent($node, 'create'); - } - - public function onUpdate(Node $node) { - $this->handleEvent($node, 'update'); - } - - public function onRename(Node $oldNode, Node $node) { - $this->handleEvent($node, 'rename', ['oldFilePath' => $oldNode->getPath()]); - } - - /** - * Register the app to several events - */ - public function registerHooksAndListeners() { - $root = $this->getContainer()->getServer()->getRootFolder(); - $root->listen('\OC\Files', 'postCreate', [$this, 'onCreate']); - $root->listen('\OC\Files', 'postWrite', [$this, 'onUpdate']); - $root->listen('\OC\Files', 'postRename', [$this, 'onRename']); + \OC::$server->getEventDispatcher()->addListener(IManager::EVENT_NAME_REG_OPERATION, function (GenericEvent $event) { + $operation = \OC::$server->query(Operation::class); + $event->getSubject()->registerOperation($operation); + \OC_Util::addScript('workflow_script', 'admin'); + }); } } diff --git a/lib/Operation.php b/lib/Operation.php index 34c523b..acd028f 100644 --- a/lib/Operation.php +++ b/lib/Operation.php @@ -23,22 +23,26 @@ namespace OCA\WorkflowScript; -use OC\Files\Node\File; -use OC\Files\Node\Folder; +use OC\Files\View; +use OCA\WorkflowEngine\Entity\File; use OCA\WorkflowScript\BackgroundJobs\Launcher; use OCP\BackgroundJob\IJobList; +use OCP\EventDispatcher\Event; +use OCP\Files\Folder; use OCP\Files\InvalidPathException; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; -use OCP\Files\NotPermittedException; use OCP\IL10N; use OCP\IUser; use OCP\IUserSession; +use OCP\SystemTag\MapperEvent; use OCP\WorkflowEngine\IManager; -use OCP\WorkflowEngine\IOperation; +use OCP\WorkflowEngine\IRuleMatcher; +use OCP\WorkflowEngine\ISpecificOperation; +use Symfony\Component\EventDispatcher\GenericEvent; -class Operation implements IOperation { +class Operation implements ISpecificOperation { /** @var IManager */ private $workflowEngineManager; @@ -59,22 +63,6 @@ public function __construct(IManager $workflowEngineManager, IJobList $jobList, $this->rootFolder = $rootFolder; } - public function considerScript(Node $node, string $event, array $extra = []) { - try { - $this->workflowEngineManager->setFileInfo($node->getStorage(), $node->getInternalPath()); - $matches = $this->workflowEngineManager->getMatchingOperations(Operation::class, false); - foreach ($matches as $match) { - $command = $this->buildCommand($match['operation'], $node, $event, $extra); - $args = ['command' => $command]; - if (strpos($command, '%f')) { - $args['path'] = $node->getPath(); - } - $this->jobList->add(Launcher::class, $args); - } - } catch (NotFoundException $e) { - } - } - protected function buildCommand(string $template, Node $node, string $event, array $extra = []) { $command = $template; @@ -89,13 +77,13 @@ protected function buildCommand(string $template, Node $node, string $event, arr if (false && strpos($command, '%f')) { try { - $view = new \OC\Files\View($node->getParent()->getPath()); - if($node instanceof \OCP\Files\Folder) { + $view = new View($node->getParent()->getPath()); + if ($node instanceof Folder) { $fullPath = $view->getLocalFolder($node->getPath()); } else { $fullPath = $view->getLocalFile($node->getPath()); } - if($fullPath === null) { + if ($fullPath === null) { throw new \InvalidArgumentException(); } //$fullPath = $node->getParent()->getFullPath($node->getPath()); @@ -144,13 +132,10 @@ protected function buildCommand(string $template, Node $node, string $event, arr } /** - * @param string $name - * @param array[] $checks - * @param string $operation * @throws \UnexpectedValueException * @since 9.1 */ - public function validateOperation($name, array $checks, $operation) { + public function validateOperation(string $name, array $checks, string $operation): void { if (empty($operation)) { throw new \UnexpectedValueException($this->l->t('Please provide a script name')); } @@ -169,4 +154,68 @@ protected function isScriptValid(string $scriptName) { return is_executable($scriptName); } + + public function getDisplayName(): string { + return $this->l->t('External scripts'); + } + + public function getDescription(): string { + return $this->l->t('Pass files to external scripts for processing outside of Nextcloud'); + } + + public function getIcon(): string { + return \OC::$server->getURLGenerator()->imagePath('workflow_script', 'app.svg'); + } + + public function isAvailableForScope(int $scope): bool { + return $scope === IManager::SCOPE_ADMIN; + } + + public function onEvent(string $eventName, Event $event, IRuleMatcher $ruleMatcher): void { + if (!$event instanceof GenericEvent && !$event instanceof MapperEvent) { + return; + } + try { + $extra = []; + if ($eventName === '\OCP\Files::postRename') { + /** @var Node $oldNode */ + list($oldNode,) = $event->getSubject(); + $extra = ['oldFilePath' => $oldNode->getPath()]; + } else if ($event instanceof MapperEvent) { + if ($event->getObjectType() !== 'files') { + return; + } + $nodes = $this->rootFolder->getById($event->getObjectId()); + if (!isset($nodes[0])) { + return; + } + $node = $nodes[0]; + unset($nodes); + } else { + $node = $event->getSubject(); + } + /** @var Node $node */ + + // '', admin, 'files', 'path/to/file.txt' + list(, , $folder,) = explode('/', $node->getPath(), 4); + if ($folder !== 'files' || $node instanceof Folder) { + return; + } + + $matches = $ruleMatcher->getMatchingOperations(Operation::class, false); + foreach ($matches as $match) { + $command = $this->buildCommand($match['operation'], $node, $eventName, $extra); + $args = ['command' => $command]; + if (strpos($command, '%f')) { + $args['path'] = $node->getPath(); + } + $this->jobList->add(Launcher::class, $args); + } + } catch (NotFoundException $e) { + } + } + + public function getEntityId(): string { + return File::class; + } } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php deleted file mode 100644 index 5ae2d6d..0000000 --- a/lib/Settings/Admin.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * @author Arthur Schiwon - * - * @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 . - * - */ - -namespace OCA\WorkflowScript\Settings; - -use OCA\WorkflowScript\AppInfo\Application; -use OCP\AppFramework\Http\TemplateResponse; -use OCP\IL10N; -use OCP\Settings\ISettings; -use OCP\Util; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; - -class Admin implements ISettings { - /** @var IL10N */ - private $l10n; - - /** @var Application */ - private $app; - - /** @var EventDispatcherInterface */ - private $eventDispatcher; - - public function __construct(IL10N $l10n, Application $app, EventDispatcherInterface $eventDispatcher) { - $this->l10n = $l10n; - $this->app = $app; - $this->eventDispatcher = $eventDispatcher; - } - - /** - * @return TemplateResponse - */ - public function getForm() { - $appName = $this->app->getContainer()->getAppName(); - $this->eventDispatcher->dispatch('OCP\WorkflowEngine::loadAdditionalSettingScripts'); - Util::addScript($appName, 'admin'); - $parameters = [ - 'appid' => $appName, - 'docs' => '', - 'heading' => $this->l10n->t('External scripts'), - 'settings-hint' => $this->l10n->t('Pass files to external scripts for processing outside of Nextcloud'), - 'description' => $this->l10n->t('Each rule group consists of one or more rules. A request matches a group if all rules evaluate to true. When a file is created, written, deleted or renamed, the file is provided to the external script. If you do not use any of the following placeholders, the file path will be added to the provided command. Eventually the scripts are launched by a background job, but Nextcloud will not keep track of them or wait for their execution to finish.'), - ]; - - return new TemplateResponse('workflowengine', 'admin', $parameters, 'blank'); - } - - /** - * @return string the section ID, e.g. 'sharing' - */ - public function getSection() { - return 'workflow_script'; - } - - /** - * @return int whether the form should be rather on the top or bottom of - * the admin section. The forms are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. - * - * E.g.: 70 - */ - public function getPriority() { - return 40; - } -} diff --git a/lib/Settings/Section.php b/lib/Settings/Section.php deleted file mode 100644 index 33023f9..0000000 --- a/lib/Settings/Section.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * @author Arthur Schiwon - * - * @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 . - * - */ - -namespace OCA\WorkflowScript\Settings; - -use OCP\IL10N; -use OCP\IURLGenerator; -use OCP\Settings\IIconSection; - -class Section implements IIconSection { - /** @var IL10N */ - private $l; - /** @var IURLGenerator */ - private $url; - - public function __construct(IL10N $l, IURLGenerator $url) { - $this->l = $l; - $this->url = $url; - } - - /** - * returns the ID of the section. It is supposed to be a lower case string, - * e.g. 'ldap' - * - * @returns string - */ - public function getID() { - return 'workflow_script'; - } - - /** - * returns the translated name as it should be displayed, e.g. 'LDAP / AD - * integration'. Use the L10N service to translate it. - * - * @return string - */ - public function getName() { - return $this->l->t('External scripts'); - } - - /** - * @return int whether the form should be rather on the top or bottom of - * the settings navigation. The sections are arranged in ascending order of - * the priority values. It is required to return a value between 0 and 99. - * - * E.g.: 70 - */ - public function getPriority() { - return 80; - } - - /** - * {@inheritdoc} - */ - public function getIcon() { - return $this->url->imagePath('workflow_script', 'app-dark.svg'); - } -}