From 423fd6c89d4bab69e5e8ab0e2dd1e6bc0195a275 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 9 Sep 2019 22:45:14 +0200 Subject: [PATCH 1/6] adjust to new WorkflowEngine Signed-off-by: Arthur Schiwon --- appinfo/app.php | 1 - appinfo/info.xml | 4 -- lib/AppInfo/Application.php | 45 ------------------- lib/Operation.php | 88 +++++++++++++++++++++++++------------ lib/Settings/Admin.php | 84 ----------------------------------- lib/Settings/Section.php | 78 -------------------------------- 6 files changed, 61 insertions(+), 239 deletions(-) delete mode 100644 lib/Settings/Admin.php delete mode 100644 lib/Settings/Section.php 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/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 9e6d842..05b6f66 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -23,12 +23,6 @@ namespace OCA\WorkflowScript\AppInfo; -use OCA\WorkflowScript\Operation; -use OCP\AppFramework\QueryException; -use OCP\Files\Folder; -use OCP\Files\Node; -use OCP\ILogger; - class Application extends \OCP\AppFramework\App { /** @@ -38,43 +32,4 @@ 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']); - } - } diff --git a/lib/Operation.php b/lib/Operation.php index 34c523b..c2147b8 100644 --- a/lib/Operation.php +++ b/lib/Operation.php @@ -23,22 +23,24 @@ 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\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\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 +61,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,8 +75,8 @@ 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()); @@ -144,13 +130,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 +152,55 @@ 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, GenericEvent $event, IRuleMatcher $ruleMatcher): void { + try { + $extra = []; + if($eventName === '\OCP\Files::postRename') { + /** @var Node $oldNode */ + list($oldNode, ) = $event->getSubject(); + $extra = ['oldFilePath' => $oldNode->getPath()]; + } 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, $event, $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'); - } -} From 8f019538f99b462096df62a0ee733fd0b60f9555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 10 Oct 2019 11:38:55 +0200 Subject: [PATCH 2/6] Add vue component for input field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- js/admin.js | 108 +++++++++++++++++----------------------------------- 1 file changed, 34 insertions(+), 74 deletions(-) diff --git a/js/admin.js b/js/admin.js index 6b0598d..22c90e3 100644 --- a/js/admin.js +++ b/js/admin.js @@ -17,81 +17,41 @@ * along with this program. If not, see . * */ - -(function() { - OCA.WorkflowScript = OCA.WorkflowScript || {}; - - /** - * @class OCA.WorkflowScript.Operation - */ - OCA.WorkflowScript.Operation = - OCA.WorkflowEngine.Operation.extend({ - defaults: { - 'class': 'OCA\\WorkflowScript\\Operation', - 'name': '', - 'checks': [], - 'operation': '' - } - }); - - /** - * @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' - ]); +var Component = { + name: 'WorkflowScript', + render: function (createElement) { + var self = this + return createElement('input', { + attrs: { + type: 'text' }, - renderOperation: function(operation) { - var subView = new OCA.WorkflowScript.OperationView({ - model: operation - }); - - OCA.WorkflowEngine.OperationsView.prototype.renderOperation.apply(this, [ - subView - ]); + domProps: { + value: self.value, + required: 'true' + }, + style: { + width: '100%' + }, + on: { + input: function (event) { + self.value = event.target.value + self.$emit('input', event.target.value) + } } - }); -})(); - - -$(document).ready(function() { - OC.SystemTags.collection.fetch({ - success: function() { - new OCA.WorkflowScript.OperationsView({ - el: '#workflow_script .rules', - collection: new OCA.WorkflowScript.OperationsCollection() - }); + }) + }, + props: { + value: '' + }, + methods: { + update: function (value) { + this.$emit('input', value) } - }); + } +}; + +OCA.WorkflowEngine.registerOperator({ + id: 'OCA\\WorkflowScript\\Operation', + operation: '', + options: Component }); From 00a2ac4a3065209b4c50e1f4fd6217f793195108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 10 Oct 2019 11:39:22 +0200 Subject: [PATCH 3/6] Move to new Operation registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- lib/AppInfo/Application.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 05b6f66..357d05b 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -23,6 +23,10 @@ namespace OCA\WorkflowScript\AppInfo; +use OCA\WorkflowScript\Operation; +use OCP\WorkflowEngine\IManager; +use Symfony\Component\EventDispatcher\GenericEvent; + class Application extends \OCP\AppFramework\App { /** @@ -30,6 +34,11 @@ class Application extends \OCP\AppFramework\App { */ public function __construct() { parent::__construct('workflow_script'); + \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'); + }); } } From f922ded04d69e62a32c3ec87239bcfad4c7ac61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 10 Oct 2019 14:49:44 +0200 Subject: [PATCH 4/6] Add description and link to the docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- js/admin.js | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/js/admin.js b/js/admin.js index 22c90e3..6b9780a 100644 --- a/js/admin.js +++ b/js/admin.js @@ -21,31 +21,45 @@ var Component = { name: 'WorkflowScript', render: function (createElement) { var self = this - return createElement('input', { - attrs: { - type: 'text' - }, - domProps: { - value: self.value, - required: 'true' - }, + return createElement('div', { style: { width: '100%' }, - on: { - input: function (event) { - self.value = event.target.value - self.$emit('input', event.target.value) + }, [ + 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: '' }, - methods: { - update: function (value) { - this.$emit('input', value) + data: function () { + return { + description: t('workflow_script', 'Available placeholder variables are listed in the documentation') + '↗', + link: 'https://github.com/nextcloud/workflow_script#placeholders' } } }; From 4801741c36d70b11be342c6e2793cc72e3ca6399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 10 Oct 2019 14:57:27 +0200 Subject: [PATCH 5/6] Keep the variable private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- js/admin.js | 97 ++++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/js/admin.js b/js/admin.js index 6b9780a..541803a 100644 --- a/js/admin.js +++ b/js/admin.js @@ -17,55 +17,60 @@ * along with this program. If not, see . * */ -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' - }, + +(function() { + + var Component = { + name: 'WorkflowScript', + render: function (createElement) { + var self = this + return createElement('div', { style: { width: '100%' }, - on: { - input: function (event) { - self.$emit('input', event.target.value) + }, [ + 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' + }), + 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' + } } - } -}; + }; + + OCA.WorkflowEngine.registerOperator({ + id: 'OCA\\WorkflowScript\\Operation', + operation: '', + options: Component + }); -OCA.WorkflowEngine.registerOperator({ - id: 'OCA\\WorkflowScript\\Operation', - operation: '', - options: Component -}); +})(); From ddb8a5dbb8bc640717d9d812c4f8258ddbf63798 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 18 Nov 2019 23:35:26 +0100 Subject: [PATCH 6/6] support mapper events and adjust onEvent signature to API Signed-off-by: Arthur Schiwon --- lib/Operation.php | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/Operation.php b/lib/Operation.php index c2147b8..acd028f 100644 --- a/lib/Operation.php +++ b/lib/Operation.php @@ -27,6 +27,7 @@ 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; @@ -35,6 +36,7 @@ use OCP\IL10N; use OCP\IUser; use OCP\IUserSession; +use OCP\SystemTag\MapperEvent; use OCP\WorkflowEngine\IManager; use OCP\WorkflowEngine\IRuleMatcher; use OCP\WorkflowEngine\ISpecificOperation; @@ -76,12 +78,12 @@ protected function buildCommand(string $template, Node $node, string $event, arr if (false && strpos($command, '%f')) { try { $view = new View($node->getParent()->getPath()); - if($node instanceof Folder) { + 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()); @@ -169,27 +171,40 @@ public function isAvailableForScope(int $scope): bool { return $scope === IManager::SCOPE_ADMIN; } - public function onEvent(string $eventName, GenericEvent $event, IRuleMatcher $ruleMatcher): void { + 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') { + if ($eventName === '\OCP\Files::postRename') { /** @var Node $oldNode */ - list($oldNode, ) = $event->getSubject(); + 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) { + 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, $event, $extra); + $command = $this->buildCommand($match['operation'], $node, $eventName, $extra); $args = ['command' => $command]; if (strpos($command, '%f')) { $args['path'] = $node->getPath();