Merge pull request #16682 from nextcloud/enh/12790/workflow-backend
workflow overhaul
This commit is contained in:
commit
2187f856ce
|
@ -21,3 +21,4 @@
|
|||
|
||||
$application = new \OCA\WorkflowEngine\AppInfo\Application();
|
||||
$application->registerHooksAndListeners();
|
||||
$application->registerRuleListeners();
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
<?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>
|
|
@ -2,11 +2,13 @@
|
|||
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
||||
<id>workflowengine</id>
|
||||
<name>Files workflow engine</name>
|
||||
<summary>Files workflow engine</summary>
|
||||
<description>Files workflow engine</description>
|
||||
<version>1.8.0</version>
|
||||
<name>Nextcloud workflow engine</name>
|
||||
<summary>Nextcloud workflow engine</summary>
|
||||
<description>Nextcloud workflow engine</description>
|
||||
<version>2.0.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Arthur Schiwon</author>
|
||||
<author>Julius Härtl</author>
|
||||
<author>Morris Jobke</author>
|
||||
<namespace>WorkflowEngine</namespace>
|
||||
|
||||
|
@ -23,7 +25,20 @@
|
|||
<nextcloud min-version="18" max-version="18"/>
|
||||
</dependencies>
|
||||
|
||||
<repair-steps>
|
||||
<post-migration>
|
||||
<step>OCA\WorkflowEngine\Migration\PopulateNewlyIntroducedDatabaseFields</step>
|
||||
</post-migration>
|
||||
</repair-steps>
|
||||
|
||||
<commands>
|
||||
<command>OCA\WorkflowEngine\Command\Index</command>
|
||||
</commands>
|
||||
|
||||
<settings>
|
||||
<admin>OCA\WorkflowEngine\Settings\Admin</admin>
|
||||
<admin-section>OCA\WorkflowEngine\Settings\Section</admin-section>
|
||||
<personal>OCA\WorkflowEngine\Settings\Personal</personal>
|
||||
<personal-section>OCA\WorkflowEngine\Settings\Section</personal-section>
|
||||
</settings>
|
||||
</info>
|
||||
|
|
|
@ -21,10 +21,9 @@
|
|||
|
||||
return [
|
||||
'routes' => [
|
||||
['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'],
|
||||
['name' => 'requestTime#getTimezones', 'url' => '/timezones', 'verb' => 'GET'],
|
||||
]
|
||||
],
|
||||
'ocs-resources' => [
|
||||
'global_workflows' => ['url' => '/api/v1/workflows/global'],
|
||||
],
|
||||
];
|
||||
|
|
|
@ -16,9 +16,21 @@ return array(
|
|||
'OCA\\WorkflowEngine\\Check\\RequestTime' => $baseDir . '/../lib/Check/RequestTime.php',
|
||||
'OCA\\WorkflowEngine\\Check\\RequestURL' => $baseDir . '/../lib/Check/RequestURL.php',
|
||||
'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => $baseDir . '/../lib/Check/RequestUserAgent.php',
|
||||
'OCA\\WorkflowEngine\\Check\\TFileCheck' => $baseDir . '/../lib/Check/TFileCheck.php',
|
||||
'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => $baseDir . '/../lib/Check/UserGroupMembership.php',
|
||||
'OCA\\WorkflowEngine\\Controller\\FlowOperations' => $baseDir . '/../lib/Controller/FlowOperations.php',
|
||||
'OCA\\WorkflowEngine\\Command\\Index' => $baseDir . '/../lib/Command/Index.php',
|
||||
'OCA\\WorkflowEngine\\Controller\\AWorkflowController' => $baseDir . '/../lib/Controller/AWorkflowController.php',
|
||||
'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => $baseDir . '/../lib/Controller/GlobalWorkflowsController.php',
|
||||
'OCA\\WorkflowEngine\\Controller\\RequestTime' => $baseDir . '/../lib/Controller/RequestTime.php',
|
||||
'OCA\\WorkflowEngine\\Controller\\UserWorkflowsController' => $baseDir . '/../lib/Controller/UserWorkflowsController.php',
|
||||
'OCA\\WorkflowEngine\\Entity\\File' => $baseDir . '/../lib/Entity/File.php',
|
||||
'OCA\\WorkflowEngine\\Helper\\ScopeContext' => $baseDir . '/../lib/Helper/ScopeContext.php',
|
||||
'OCA\\WorkflowEngine\\Manager' => $baseDir . '/../lib/Manager.php',
|
||||
'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => $baseDir . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php',
|
||||
'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => $baseDir . '/../lib/Migration/Version2019Date20190808074233.php',
|
||||
'OCA\\WorkflowEngine\\Service\\RuleMatcher' => $baseDir . '/../lib/Service/RuleMatcher.php',
|
||||
'OCA\\WorkflowEngine\\Settings\\ASettings' => $baseDir . '/../lib/Settings/ASettings.php',
|
||||
'OCA\\WorkflowEngine\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php',
|
||||
'OCA\\WorkflowEngine\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
|
||||
'OCA\\WorkflowEngine\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php',
|
||||
);
|
||||
|
|
|
@ -31,10 +31,22 @@ class ComposerStaticInitWorkflowEngine
|
|||
'OCA\\WorkflowEngine\\Check\\RequestTime' => __DIR__ . '/..' . '/../lib/Check/RequestTime.php',
|
||||
'OCA\\WorkflowEngine\\Check\\RequestURL' => __DIR__ . '/..' . '/../lib/Check/RequestURL.php',
|
||||
'OCA\\WorkflowEngine\\Check\\RequestUserAgent' => __DIR__ . '/..' . '/../lib/Check/RequestUserAgent.php',
|
||||
'OCA\\WorkflowEngine\\Check\\TFileCheck' => __DIR__ . '/..' . '/../lib/Check/TFileCheck.php',
|
||||
'OCA\\WorkflowEngine\\Check\\UserGroupMembership' => __DIR__ . '/..' . '/../lib/Check/UserGroupMembership.php',
|
||||
'OCA\\WorkflowEngine\\Controller\\FlowOperations' => __DIR__ . '/..' . '/../lib/Controller/FlowOperations.php',
|
||||
'OCA\\WorkflowEngine\\Command\\Index' => __DIR__ . '/..' . '/../lib/Command/Index.php',
|
||||
'OCA\\WorkflowEngine\\Controller\\AWorkflowController' => __DIR__ . '/..' . '/../lib/Controller/AWorkflowController.php',
|
||||
'OCA\\WorkflowEngine\\Controller\\GlobalWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/GlobalWorkflowsController.php',
|
||||
'OCA\\WorkflowEngine\\Controller\\RequestTime' => __DIR__ . '/..' . '/../lib/Controller/RequestTime.php',
|
||||
'OCA\\WorkflowEngine\\Controller\\UserWorkflowsController' => __DIR__ . '/..' . '/../lib/Controller/UserWorkflowsController.php',
|
||||
'OCA\\WorkflowEngine\\Entity\\File' => __DIR__ . '/..' . '/../lib/Entity/File.php',
|
||||
'OCA\\WorkflowEngine\\Helper\\ScopeContext' => __DIR__ . '/..' . '/../lib/Helper/ScopeContext.php',
|
||||
'OCA\\WorkflowEngine\\Manager' => __DIR__ . '/..' . '/../lib/Manager.php',
|
||||
'OCA\\WorkflowEngine\\Migration\\PopulateNewlyIntroducedDatabaseFields' => __DIR__ . '/..' . '/../lib/Migration/PopulateNewlyIntroducedDatabaseFields.php',
|
||||
'OCA\\WorkflowEngine\\Migration\\Version2019Date20190808074233' => __DIR__ . '/..' . '/../lib/Migration/Version2019Date20190808074233.php',
|
||||
'OCA\\WorkflowEngine\\Service\\RuleMatcher' => __DIR__ . '/..' . '/../lib/Service/RuleMatcher.php',
|
||||
'OCA\\WorkflowEngine\\Settings\\ASettings' => __DIR__ . '/..' . '/../lib/Settings/ASettings.php',
|
||||
'OCA\\WorkflowEngine\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php',
|
||||
'OCA\\WorkflowEngine\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
|
||||
'OCA\\WorkflowEngine\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php',
|
||||
);
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -21,25 +21,37 @@
|
|||
|
||||
namespace OCA\WorkflowEngine\AppInfo;
|
||||
|
||||
use OCA\WorkflowEngine\Manager;
|
||||
use OCP\Template;
|
||||
use OCA\WorkflowEngine\Controller\RequestTime;
|
||||
use OCA\WorkflowEngine\Controller\FlowOperations;
|
||||
use OCP\WorkflowEngine\IEntity;
|
||||
use OCP\WorkflowEngine\IOperation;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
|
||||
class Application extends \OCP\AppFramework\App {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('workflowengine');
|
||||
const APP_ID = 'workflowengine';
|
||||
|
||||
/** @var EventDispatcherInterface */
|
||||
protected $dispatcher;
|
||||
/** @var Manager */
|
||||
protected $manager;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(self::APP_ID);
|
||||
|
||||
$this->getContainer()->registerAlias('FlowOperationsController', FlowOperations::class);
|
||||
$this->getContainer()->registerAlias('RequestTimeController', RequestTime::class);
|
||||
|
||||
$this->dispatcher = $this->getContainer()->getServer()->getEventDispatcher();
|
||||
$this->manager = $this->getContainer()->query(Manager::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all hooks and listeners
|
||||
*/
|
||||
public function registerHooksAndListeners() {
|
||||
$dispatcher = $this->getContainer()->getServer()->getEventDispatcher();
|
||||
$dispatcher->addListener(
|
||||
$this->dispatcher->addListener(
|
||||
'OCP\WorkflowEngine::loadAdditionalSettingScripts',
|
||||
function() {
|
||||
if (!function_exists('style')) {
|
||||
|
@ -47,7 +59,7 @@ class Application extends \OCP\AppFramework\App {
|
|||
class_exists(Template::class, true);
|
||||
}
|
||||
|
||||
style('workflowengine', [
|
||||
style(self::APP_ID, [
|
||||
'admin',
|
||||
]);
|
||||
|
||||
|
@ -59,11 +71,34 @@ class Application extends \OCP\AppFramework\App {
|
|||
'systemtags/systemtagscollection',
|
||||
]);
|
||||
|
||||
script('workflowengine', [
|
||||
script(self::APP_ID, [
|
||||
'workflowengine',
|
||||
]);
|
||||
},
|
||||
-100
|
||||
);
|
||||
}
|
||||
|
||||
public function registerRuleListeners() {
|
||||
$configuredEvents = $this->manager->getAllConfiguredEvents();
|
||||
|
||||
foreach ($configuredEvents as $operationClass => $events) {
|
||||
foreach ($events as $entityClass => $eventNames) {
|
||||
array_map(function (string $eventName) use ($operationClass, $entityClass) {
|
||||
$this->dispatcher->addListener(
|
||||
$eventName,
|
||||
function (GenericEvent $event) use ($eventName, $operationClass, $entityClass) {
|
||||
$ruleMatcher = $this->manager->getRuleMatcher();
|
||||
/** @var IEntity $entity */
|
||||
$entity = $this->getContainer()->query($entityClass);
|
||||
$entity->prepareRuleMatcher($ruleMatcher, $eventName, $event);
|
||||
/** @var IOperation $operation */
|
||||
$operation = $this->getContainer()->query($operationClass);
|
||||
$operation->onEvent($eventName, $event, $ruleMatcher);
|
||||
}
|
||||
);
|
||||
}, $eventNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
namespace OCA\WorkflowEngine\Check;
|
||||
|
||||
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IL10N;
|
||||
use OCP\WorkflowEngine\ICheck;
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
|
||||
abstract class AbstractStringCheck implements ICheck {
|
||||
|
||||
|
@ -41,14 +41,6 @@ abstract class AbstractStringCheck implements ICheck {
|
|||
$this->l = $l;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IStorage $storage
|
||||
* @param string $path
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, $path) {
|
||||
// Nothing changes here with a different path
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
@ -101,6 +93,16 @@ abstract class AbstractStringCheck implements ICheck {
|
|||
}
|
||||
}
|
||||
|
||||
public function supportedEntities(): array {
|
||||
// universal by default
|
||||
return [];
|
||||
}
|
||||
|
||||
public function isAvailableForScope(int $scope): bool {
|
||||
// admin only by default
|
||||
return $scope === IManager::SCOPE_ADMIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pattern
|
||||
* @param string $subject
|
||||
|
|
|
@ -22,12 +22,17 @@
|
|||
namespace OCA\WorkflowEngine\Check;
|
||||
|
||||
|
||||
use OCA\WorkflowEngine\Entity\File;
|
||||
use OCP\Files\IMimeTypeDetector;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\WorkflowEngine\IFileCheck;
|
||||
|
||||
class FileMimeType extends AbstractStringCheck {
|
||||
class FileMimeType extends AbstractStringCheck implements IFileCheck {
|
||||
use TFileCheck {
|
||||
setFileInfo as _setFileInfo;
|
||||
}
|
||||
|
||||
/** @var array */
|
||||
protected $mimeType;
|
||||
|
@ -38,12 +43,6 @@ class FileMimeType extends AbstractStringCheck {
|
|||
/** @var IMimeTypeDetector */
|
||||
protected $mimeTypeDetector;
|
||||
|
||||
/** @var IStorage */
|
||||
protected $storage;
|
||||
|
||||
/** @var string */
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @param IL10N $l
|
||||
* @param IRequest $request
|
||||
|
@ -59,9 +58,8 @@ class FileMimeType extends AbstractStringCheck {
|
|||
* @param IStorage $storage
|
||||
* @param string $path
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, $path) {
|
||||
$this->storage = $storage;
|
||||
$this->path = $path;
|
||||
public function setFileInfo(IStorage $storage, string $path) {
|
||||
$this->_setFileInfo($storage, $path);
|
||||
if (!isset($this->mimeType[$this->storage->getId()][$this->path])
|
||||
|| $this->mimeType[$this->storage->getId()][$this->path] === '') {
|
||||
$this->mimeType[$this->storage->getId()][$this->path] = null;
|
||||
|
@ -195,4 +193,8 @@ class FileMimeType extends AbstractStringCheck {
|
|||
strpos($this->request->getPathInfo(), '/webdav/') === 0
|
||||
);
|
||||
}
|
||||
|
||||
public function supportedEntities(): array {
|
||||
return [ File::class ];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,21 +22,17 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\WorkflowEngine\Check;
|
||||
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCA\WorkflowEngine\Entity\File;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\WorkflowEngine\IFileCheck;
|
||||
|
||||
class FileName extends AbstractStringCheck {
|
||||
class FileName extends AbstractStringCheck implements IFileCheck {
|
||||
use TFileCheck;
|
||||
|
||||
/** @var IRequest */
|
||||
protected $request;
|
||||
|
||||
/** @var IStorage */
|
||||
protected $storage;
|
||||
|
||||
/** @var string */
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @param IL10N $l
|
||||
* @param IRequest $request
|
||||
|
@ -46,15 +42,6 @@ class FileName extends AbstractStringCheck {
|
|||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IStorage $storage
|
||||
* @param string $path
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, $path) {
|
||||
$this->storage = $storage;
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
@ -75,4 +62,12 @@ class FileName extends AbstractStringCheck {
|
|||
}
|
||||
return parent::executeStringCheck($operator, $checkValue, $actualValue);
|
||||
}
|
||||
|
||||
public function supportedEntities(): array {
|
||||
return [ File::class ];
|
||||
}
|
||||
|
||||
public function isAvailableForScope(int $scope): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,12 @@
|
|||
namespace OCA\WorkflowEngine\Check;
|
||||
|
||||
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCA\WorkflowEngine\Entity\File;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\Util;
|
||||
use OCP\WorkflowEngine\ICheck;
|
||||
use OCP\WorkflowEngine\IEntity;
|
||||
|
||||
class FileSize implements ICheck {
|
||||
|
||||
|
@ -48,13 +49,6 @@ class FileSize implements ICheck {
|
|||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IStorage $storage
|
||||
* @param string $path
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, $path) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $operator
|
||||
* @param string $value
|
||||
|
@ -116,4 +110,12 @@ class FileSize implements ICheck {
|
|||
$this->size = $size;
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function supportedEntities(): array {
|
||||
return [ File::class ];
|
||||
}
|
||||
|
||||
public function isAvailableForScope(int $scope): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,16 +22,18 @@
|
|||
namespace OCA\WorkflowEngine\Check;
|
||||
|
||||
|
||||
use OCA\WorkflowEngine\Entity\File;
|
||||
use OCP\Files\Cache\ICache;
|
||||
use OCP\Files\IHomeStorage;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IL10N;
|
||||
use OCP\SystemTag\ISystemTagManager;
|
||||
use OCP\SystemTag\ISystemTagObjectMapper;
|
||||
use OCP\SystemTag\TagNotFoundException;
|
||||
use OCP\WorkflowEngine\ICheck;
|
||||
use OCP\WorkflowEngine\IFileCheck;
|
||||
|
||||
class FileSystemTags implements ICheck {
|
||||
class FileSystemTags implements ICheck, IFileCheck {
|
||||
use TFileCheck;
|
||||
|
||||
/** @var array */
|
||||
protected $fileIds;
|
||||
|
@ -48,12 +50,6 @@ class FileSystemTags implements ICheck {
|
|||
/** @var ISystemTagObjectMapper */
|
||||
protected $systemTagObjectMapper;
|
||||
|
||||
/** @var IStorage */
|
||||
protected $storage;
|
||||
|
||||
/** @var string */
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @param IL10N $l
|
||||
* @param ISystemTagManager $systemTagManager
|
||||
|
@ -65,15 +61,6 @@ class FileSystemTags implements ICheck {
|
|||
$this->systemTagObjectMapper = $systemTagObjectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IStorage $storage
|
||||
* @param string $path
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, $path) {
|
||||
$this->storage = $storage;
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $operator
|
||||
* @param string $value
|
||||
|
@ -166,4 +153,12 @@ class FileSystemTags implements ICheck {
|
|||
$dir = dirname($path);
|
||||
return $dir === '.' ? '' : $dir;
|
||||
}
|
||||
|
||||
public function supportedEntities(): array {
|
||||
return [ File::class ];
|
||||
}
|
||||
|
||||
public function isAvailableForScope(int $scope): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
namespace OCA\WorkflowEngine\Check;
|
||||
|
||||
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\WorkflowEngine\ICheck;
|
||||
|
@ -44,14 +43,6 @@ class RequestRemoteAddress implements ICheck {
|
|||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IStorage $storage
|
||||
* @param string $path
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, $path) {
|
||||
// A different path doesn't change time, so nothing to do here.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $operator
|
||||
* @param string $value
|
||||
|
@ -151,4 +142,32 @@ class RequestRemoteAddress implements ICheck {
|
|||
}
|
||||
return str_pad($binaryIp, 128, '0', STR_PAD_RIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a list of Entities the checker supports. The values must match
|
||||
* the class name of the entity.
|
||||
*
|
||||
* An empty result means the check is universally available.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function supportedEntities(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns whether the operation can be used in the requested scope.
|
||||
*
|
||||
* Scope IDs are defined as constants in OCP\WorkflowEngine\IManager. At
|
||||
* time of writing these are SCOPE_ADMIN and SCOPE_USER.
|
||||
*
|
||||
* For possibly unknown future scopes the recommended behaviour is: if
|
||||
* user scope is permitted, the default behaviour should return `true`,
|
||||
* otherwise `false`.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function isAvailableForScope(int $scope): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ namespace OCA\WorkflowEngine\Check;
|
|||
|
||||
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IL10N;
|
||||
use OCP\WorkflowEngine\ICheck;
|
||||
|
||||
|
@ -49,14 +48,6 @@ class RequestTime implements ICheck {
|
|||
$this->timeFactory = $timeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IStorage $storage
|
||||
* @param string $path
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, $path) {
|
||||
// A different path doesn't change time, so nothing to do here.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $operator
|
||||
* @param string $value
|
||||
|
@ -126,4 +117,20 @@ class RequestTime implements ICheck {
|
|||
throw new \UnexpectedValueException($this->l->t('The given end time is invalid'), 4);
|
||||
}
|
||||
}
|
||||
|
||||
public function isAvailableForScope(int $scope): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a list of Entities the checker supports. The values must match
|
||||
* the class name of the entity.
|
||||
*
|
||||
* An empty result means the check is universally available.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function supportedEntities(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,4 +79,9 @@ class RequestUserAgent extends AbstractStringCheck {
|
|||
protected function getActualValue() {
|
||||
return (string) $this->request->getHeader('User-Agent');
|
||||
}
|
||||
|
||||
public function isAvailableForScope(int $scope): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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 OCA\WorkflowEngine\AppInfo\Application;
|
||||
use OCA\WorkflowEngine\Entity\File;
|
||||
use OCP\Files\Node;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\WorkflowEngine\IEntity;
|
||||
|
||||
trait TFileCheck {
|
||||
/** @var IStorage */
|
||||
protected $storage;
|
||||
|
||||
/** @var string */
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @param IStorage $storage
|
||||
* @param string $path
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, string $path) {
|
||||
$this->storage = $storage;
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \OCP\Files\NotFoundException
|
||||
*/
|
||||
public function setEntitySubject(IEntity $entity, $subject): void {
|
||||
if ($entity instanceof File) {
|
||||
if (!$subject instanceof Node) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Expected Node subject for File entity, got {class}',
|
||||
['app' => Application::APP_ID, 'class' => get_class($subject)]
|
||||
);
|
||||
}
|
||||
$this->storage = $subject->getStorage();
|
||||
$this->path = $subject->getPath();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,12 +22,12 @@
|
|||
namespace OCA\WorkflowEngine\Check;
|
||||
|
||||
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
use OCP\WorkflowEngine\ICheck;
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
|
||||
class UserGroupMembership implements ICheck {
|
||||
|
||||
|
@ -57,14 +57,6 @@ class UserGroupMembership implements ICheck {
|
|||
$this->l = $l;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
@ -111,4 +103,14 @@ class UserGroupMembership implements ICheck {
|
|||
|
||||
return $this->cachedGroupMemberships;
|
||||
}
|
||||
|
||||
public function supportedEntities(): array {
|
||||
// universal by default
|
||||
return [];
|
||||
}
|
||||
|
||||
public function isAvailableForScope(int $scope): bool {
|
||||
// admin only by default
|
||||
return $scope === IManager::SCOPE_ADMIN;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Command;
|
||||
|
||||
use OCA\WorkflowEngine\Helper\ScopeContext;
|
||||
use OCA\WorkflowEngine\Manager;
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Index extends Command {
|
||||
|
||||
/** @var Manager */
|
||||
private $manager;
|
||||
|
||||
public function __construct(Manager $manager) {
|
||||
$this->manager = $manager;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure() {
|
||||
$this
|
||||
->setName('workflows:list')
|
||||
->setDescription('Lists configured workflows')
|
||||
->addArgument(
|
||||
'scope',
|
||||
InputArgument::OPTIONAL,
|
||||
'Lists workflows for "admin", "user"',
|
||||
'admin'
|
||||
)
|
||||
->addArgument(
|
||||
'scopeId',
|
||||
InputArgument::OPTIONAL,
|
||||
'User IDs when the scope is "user"',
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
protected function mappedScope(string $scope): int {
|
||||
static $scopes = [
|
||||
'admin' => IManager::SCOPE_ADMIN,
|
||||
'user' => IManager::SCOPE_USER,
|
||||
];
|
||||
return $scopes[$scope] ?? -1;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$ops = $this->manager->getAllOperations(
|
||||
new ScopeContext(
|
||||
$this->mappedScope($input->getArgument('scope')),
|
||||
$input->getArgument('scopeId')
|
||||
)
|
||||
);
|
||||
$output->writeln(\json_encode($ops));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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 Doctrine\DBAL\DBALException;
|
||||
use OCA\WorkflowEngine\Helper\ScopeContext;
|
||||
use OCA\WorkflowEngine\Manager;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCS\OCSBadRequestException;
|
||||
use OCP\AppFramework\OCS\OCSException;
|
||||
use OCP\AppFramework\OCS\OCSForbiddenException;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IRequest;
|
||||
|
||||
abstract class AWorkflowController extends OCSController {
|
||||
|
||||
/** @var Manager */
|
||||
protected $manager;
|
||||
|
||||
public function __construct(
|
||||
$appName,
|
||||
IRequest $request,
|
||||
Manager $manager
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
|
||||
$this->manager = $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
abstract protected function getScopeContext(): ScopeContext;
|
||||
|
||||
/**
|
||||
* Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global?format=json"
|
||||
*
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
public function index(): DataResponse {
|
||||
$operationsByClass = $this->manager->getAllOperations($this->getScopeContext());
|
||||
|
||||
foreach ($operationsByClass as &$operations) {
|
||||
foreach ($operations as &$operation) {
|
||||
$operation = $this->manager->formatOperation($operation);
|
||||
}
|
||||
}
|
||||
|
||||
return new DataResponse($operationsByClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/global/OCA\\Workflow_DocToPdf\\Operation?format=json"
|
||||
*
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
public function show(string $id): DataResponse {
|
||||
$context = $this->getScopeContext();
|
||||
|
||||
// The ID corresponds to a class name
|
||||
$operations = $this->manager->getOperations($id, $context);
|
||||
|
||||
foreach ($operations as &$operation) {
|
||||
$operation = $this->manager->formatOperation($operation);
|
||||
}
|
||||
|
||||
return new DataResponse($operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OCSBadRequestException
|
||||
* @throws OCSForbiddenException
|
||||
* @throws OCSException
|
||||
*/
|
||||
public function create(
|
||||
string $class,
|
||||
string $name,
|
||||
array $checks,
|
||||
string $operation,
|
||||
string $entity,
|
||||
array $events
|
||||
): DataResponse {
|
||||
$context = $this->getScopeContext();
|
||||
try {
|
||||
$operation = $this->manager->addOperation($class, $name, $checks, $operation, $context, $entity, $events);
|
||||
$operation = $this->manager->formatOperation($operation);
|
||||
return new DataResponse($operation);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
throw new OCSBadRequestException($e->getMessage(), $e);
|
||||
} catch (\DomainException $e) {
|
||||
throw new OCSForbiddenException($e->getMessage(), $e);
|
||||
} catch(DBALException $e) {
|
||||
throw new OCSException('An internal error occurred', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OCSBadRequestException
|
||||
* @throws OCSForbiddenException
|
||||
* @throws OCSException
|
||||
*/
|
||||
public function update(
|
||||
int $id,
|
||||
string $name,
|
||||
array $checks,
|
||||
string $operation,
|
||||
string $entity,
|
||||
array $events
|
||||
): DataResponse {
|
||||
try {
|
||||
$context = $this->getScopeContext();
|
||||
$operation = $this->manager->updateOperation($id, $name, $checks, $operation, $context, $entity, $events);
|
||||
$operation = $this->manager->formatOperation($operation);
|
||||
return new DataResponse($operation);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
throw new OCSBadRequestException($e->getMessage(), $e);
|
||||
} catch (\DomainException $e) {
|
||||
throw new OCSForbiddenException($e->getMessage(), $e);
|
||||
} catch(DBALException $e) {
|
||||
throw new OCSException('An internal error occurred', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OCSBadRequestException
|
||||
* @throws OCSForbiddenException
|
||||
* @throws OCSException
|
||||
*/
|
||||
public function destroy(int $id): DataResponse {
|
||||
try {
|
||||
$deleted = $this->manager->deleteOperation($id, $this->getScopeContext());
|
||||
return new DataResponse($deleted);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
throw new OCSBadRequestException($e->getMessage(), $e);
|
||||
} catch (\DomainException $e) {
|
||||
throw new OCSForbiddenException($e->getMessage(), $e);
|
||||
} catch(DBALException $e) {
|
||||
throw new OCSException('An internal error occurred', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
<?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;
|
||||
|
||||
class FlowOperations extends Controller {
|
||||
|
||||
/** @var Manager */
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* @param IRequest $request
|
||||
* @param Manager $manager
|
||||
*/
|
||||
public function __construct(IRequest $request, Manager $manager) {
|
||||
parent::__construct('workflowengine', $request);
|
||||
$this->manager = $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @PasswordConfirmationRequired
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @PasswordConfirmationRequired
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @PasswordConfirmationRequired
|
||||
*
|
||||
* @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'], true);
|
||||
$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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Helper\ScopeContext;
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
|
||||
class GlobalWorkflowsController extends AWorkflowController {
|
||||
|
||||
/** @var ScopeContext */
|
||||
private $scopeContext;
|
||||
|
||||
protected function getScopeContext(): ScopeContext {
|
||||
if($this->scopeContext === null) {
|
||||
$this->scopeContext = new ScopeContext(IManager::SCOPE_ADMIN);
|
||||
}
|
||||
return $this->scopeContext;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Helper\ScopeContext;
|
||||
use OCA\WorkflowEngine\Manager;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCS\OCSBadRequestException;
|
||||
use OCP\AppFramework\OCS\OCSForbiddenException;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
|
||||
class UserWorkflowsController extends AWorkflowController {
|
||||
|
||||
/** @var IUserSession */
|
||||
private $session;
|
||||
|
||||
/** @var ScopeContext */
|
||||
private $scopeContext;
|
||||
|
||||
public function __construct(
|
||||
$appName,
|
||||
IRequest $request,
|
||||
Manager $manager,
|
||||
IUserSession $session
|
||||
) {
|
||||
parent::__construct($appName, $request, $manager);
|
||||
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all configured workflow rules
|
||||
*
|
||||
* Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/user?format=json"
|
||||
*
|
||||
* @NoAdminRequired
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
public function index(): DataResponse {
|
||||
return parent::index();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* Example: curl -u joann -H "OCS-APIREQUEST: true" "http://my.nc.srvr/ocs/v2.php/apps/workflowengine/api/v1/workflows/user/OCA\\Workflow_DocToPdf\\Operation?format=json"
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
public function show(string $id): DataResponse {
|
||||
return parent::show($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @throws OCSBadRequestException
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
public function create(string $class, string $name, array $checks, string $operation): DataResponse {
|
||||
return parent::create($class, $name, $checks, $operation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @throws OCSBadRequestException
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
public function update(int $id, string $name, array $checks, string $operation): DataResponse {
|
||||
return parent::update($id, $name, $checks, $operation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
public function destroy(int $id): DataResponse {
|
||||
return parent::destroy($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
protected function getScopeContext(): ScopeContext {
|
||||
if($this->scopeContext === null) {
|
||||
$user = $this->session->getUser();
|
||||
if(!$user) {
|
||||
throw new OCSForbiddenException('User not logged in');
|
||||
}
|
||||
$this->scopeContext = new ScopeContext(IManager::SCOPE_USER, $user->getUID());
|
||||
}
|
||||
return $this->scopeContext;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Entity;
|
||||
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\SystemTag\MapperEvent;
|
||||
use OCP\WorkflowEngine\GenericEntityEvent;
|
||||
use OCP\WorkflowEngine\IEntity;
|
||||
use OCP\WorkflowEngine\IRuleMatcher;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
|
||||
class File implements IEntity {
|
||||
|
||||
/** @var IL10N */
|
||||
protected $l10n;
|
||||
/** @var IURLGenerator */
|
||||
protected $urlGenerator;
|
||||
/** @var IRootFolder */
|
||||
private $root;
|
||||
|
||||
public function __construct(IL10N $l10n, IURLGenerator $urlGenerator, IRootFolder $root) {
|
||||
$this->l10n = $l10n;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->root = $root;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->l10n->t('File');
|
||||
}
|
||||
|
||||
public function getIcon(): string {
|
||||
return $this->urlGenerator->imagePath('core', 'categories/files.svg');
|
||||
}
|
||||
|
||||
public function getEvents(): array {
|
||||
$namespace = '\OCP\Files::';
|
||||
return [
|
||||
new GenericEntityEvent($this->l10n->t('File created'), $namespace . 'postCreate' ),
|
||||
new GenericEntityEvent($this->l10n->t('File updated'), $namespace . 'postWrite' ),
|
||||
new GenericEntityEvent($this->l10n->t('File renamed'), $namespace . 'postRename' ),
|
||||
new GenericEntityEvent($this->l10n->t('File deleted'), $namespace . 'postDelete' ),
|
||||
new GenericEntityEvent($this->l10n->t('File accessed'), $namespace . 'postTouch' ),
|
||||
new GenericEntityEvent($this->l10n->t('File copied'), $namespace . 'postCopy' ),
|
||||
new GenericEntityEvent($this->l10n->t('Tag assigned'), MapperEvent::EVENT_ASSIGN ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function prepareRuleMatcher(IRuleMatcher $ruleMatcher, string $eventName, GenericEvent $event): void {
|
||||
switch ($eventName) {
|
||||
case 'postCreate':
|
||||
case 'postWrite':
|
||||
case 'postDelete':
|
||||
case 'postTouch':
|
||||
$ruleMatcher->setEntitySubject($this, $event->getSubject());
|
||||
break;
|
||||
case 'postRename':
|
||||
case 'postCopy':
|
||||
$ruleMatcher->setEntitySubject($this, $event->getSubject()[1]);
|
||||
break;
|
||||
case MapperEvent::EVENT_ASSIGN:
|
||||
if(!$event instanceof MapperEvent || $event->getObjectType() !== 'files') {
|
||||
break;
|
||||
}
|
||||
$nodes = $this->root->getById((int)$event->getObjectId());
|
||||
if(is_array($nodes) && !empty($nodes)) {
|
||||
$node = array_shift($nodes);
|
||||
$ruleMatcher->setEntitySubject($this, $node);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Helper;
|
||||
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
|
||||
class ScopeContext {
|
||||
/** @var int */
|
||||
private $scope;
|
||||
/** @var string */
|
||||
private $scopeId;
|
||||
/** @var string */
|
||||
private $hash;
|
||||
|
||||
public function __construct(int $scope, string $scopeId = null) {
|
||||
$this->scope = $this->evaluateScope($scope);
|
||||
$this->scopeId = $this->evaluateScopeId($scopeId);
|
||||
}
|
||||
|
||||
private function evaluateScope(int $scope): int {
|
||||
if(in_array($scope, [IManager::SCOPE_ADMIN, IManager::SCOPE_USER], true)) {
|
||||
return $scope;
|
||||
}
|
||||
throw new \InvalidArgumentException('Invalid scope');
|
||||
}
|
||||
|
||||
private function evaluateScopeId(string $scopeId = null): string {
|
||||
if($this->scope === IManager::SCOPE_USER
|
||||
&& trim((string)$scopeId) === '')
|
||||
{
|
||||
throw new \InvalidArgumentException('user scope requires a user id');
|
||||
}
|
||||
return trim((string)$scopeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getScope(): int {
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getScopeId(): string {
|
||||
return $this->scopeId;
|
||||
}
|
||||
|
||||
public function getHash(): string {
|
||||
if($this->hash === null) {
|
||||
$this->hash = \hash('sha256', $this->getScope() . '::' . $this->getScopeId());
|
||||
}
|
||||
return $this->hash;
|
||||
}
|
||||
}
|
|
@ -21,17 +21,37 @@
|
|||
|
||||
namespace OCA\WorkflowEngine;
|
||||
|
||||
|
||||
use OC\Files\Storage\Wrapper\Jail;
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use OC\Cache\CappedMemoryCache;
|
||||
use OCA\WorkflowEngine\Check\FileMimeType;
|
||||
use OCA\WorkflowEngine\Check\FileName;
|
||||
use OCA\WorkflowEngine\Check\FileSize;
|
||||
use OCA\WorkflowEngine\Check\FileSystemTags;
|
||||
use OCA\WorkflowEngine\Check\RequestRemoteAddress;
|
||||
use OCA\WorkflowEngine\Check\RequestTime;
|
||||
use OCA\WorkflowEngine\Check\RequestURL;
|
||||
use OCA\WorkflowEngine\Check\RequestUserAgent;
|
||||
use OCA\WorkflowEngine\Check\UserGroupMembership;
|
||||
use OCA\WorkflowEngine\Entity\File;
|
||||
use OCA\WorkflowEngine\Helper\ScopeContext;
|
||||
use OCA\WorkflowEngine\Service\RuleMatcher;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
use OCP\ILogger;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\IUserSession;
|
||||
use OCP\WorkflowEngine\ICheck;
|
||||
use OCP\WorkflowEngine\IComplexOperation;
|
||||
use OCP\WorkflowEngine\IEntity;
|
||||
use OCP\WorkflowEngine\IEntityEvent;
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
use OCP\WorkflowEngine\IOperation;
|
||||
use OCP\WorkflowEngine\IRuleMatcher;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
|
||||
class Manager implements IManager {
|
||||
|
||||
|
@ -41,6 +61,9 @@ class Manager implements IManager {
|
|||
/** @var string */
|
||||
protected $path;
|
||||
|
||||
/** @var object */
|
||||
protected $entity;
|
||||
|
||||
/** @var array[] */
|
||||
protected $operations = [];
|
||||
|
||||
|
@ -56,100 +79,114 @@ class Manager implements IManager {
|
|||
/** @var IL10N */
|
||||
protected $l;
|
||||
|
||||
/** @var EventDispatcherInterface */
|
||||
protected $eventDispatcher;
|
||||
|
||||
/** @var IEntity[] */
|
||||
protected $registeredEntities = [];
|
||||
|
||||
/** @var IOperation[] */
|
||||
protected $registeredOperators = [];
|
||||
|
||||
/** @var ICheck[] */
|
||||
protected $registeredChecks = [];
|
||||
|
||||
/** @var ILogger */
|
||||
protected $logger;
|
||||
|
||||
/** @var CappedMemoryCache */
|
||||
protected $operationsByScope = [];
|
||||
|
||||
/** @var IUserSession */
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* @param IDBConnection $connection
|
||||
* @param IServerContainer $container
|
||||
* @param IL10N $l
|
||||
*/
|
||||
public function __construct(IDBConnection $connection, IServerContainer $container, IL10N $l) {
|
||||
public function __construct(
|
||||
IDBConnection $connection,
|
||||
IServerContainer $container,
|
||||
IL10N $l,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
ILogger $logger,
|
||||
IUserSession $session
|
||||
) {
|
||||
$this->connection = $connection;
|
||||
$this->container = $container;
|
||||
$this->l = $l;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->logger = $logger;
|
||||
$this->operationsByScope = new CappedMemoryCache(64);
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, $path) {
|
||||
$this->storage = $storage;
|
||||
|
||||
if ($storage->instanceOfStorage(Jail::class)) {
|
||||
$path = $storage->getJailedPath($path);
|
||||
}
|
||||
$this->path = $path;
|
||||
public function getRuleMatcher(): IRuleMatcher {
|
||||
return new RuleMatcher($this->session, $this->container, $this->l, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true) {
|
||||
$operations = $this->getOperations($class);
|
||||
public function getAllConfiguredEvents() {
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
|
||||
$matches = [];
|
||||
foreach ($operations as $operation) {
|
||||
$checkIds = json_decode($operation['checks'], true);
|
||||
$checks = $this->getChecks($checkIds);
|
||||
$query->selectDistinct('class')
|
||||
->addSelect('entity', 'events')
|
||||
->from('flow_operations')
|
||||
->where($query->expr()->neq('events', $query->createNamedParameter('[]'), IQueryBuilder::PARAM_STR));
|
||||
|
||||
foreach ($checks as $check) {
|
||||
if (!$this->check($check)) {
|
||||
// Check did not match, continue with the next operation
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$result = $query->execute();
|
||||
$operations = [];
|
||||
while($row = $result->fetch()) {
|
||||
$eventNames = \json_decode($row['events']);
|
||||
|
||||
if ($returnFirstMatchingOperationOnly) {
|
||||
return $operation;
|
||||
}
|
||||
$matches[] = $operation;
|
||||
$operation = $row['class'];
|
||||
$entity = $row['entity'];
|
||||
|
||||
$operations[$operation] = $operations[$row['class']] ?? [];
|
||||
$operations[$operation][$entity] = $operations[$operation][$entity] ?? [];
|
||||
|
||||
$operations[$operation][$entity] = array_unique(array_merge($operations[$operation][$entity], $eventNames));
|
||||
}
|
||||
$result->closeCursor();
|
||||
|
||||
return $matches;
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @return array[]
|
||||
*/
|
||||
public function getOperations($class) {
|
||||
if (isset($this->operations[$class])) {
|
||||
return $this->operations[$class];
|
||||
public function getAllOperations(ScopeContext $scopeContext): array {
|
||||
if(isset($this->operations[$scopeContext->getHash()])) {
|
||||
return $this->operations[$scopeContext->getHash()];
|
||||
}
|
||||
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
|
||||
$query->select('*')
|
||||
->from('flow_operations')
|
||||
->where($query->expr()->eq('class', $query->createNamedParameter($class)));
|
||||
$query->select('o.*')
|
||||
->from('flow_operations', 'o')
|
||||
->leftJoin('o', 'flow_operations_scope', 's', $query->expr()->eq('o.id', 's.operation_id'))
|
||||
->where($query->expr()->eq('s.type', $query->createParameter('scope')));
|
||||
|
||||
if($scopeContext->getScope() === IManager::SCOPE_USER) {
|
||||
$query->andWhere($query->expr()->eq('s.value', $query->createParameter('scopeId')));
|
||||
}
|
||||
|
||||
$query->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
|
||||
$result = $query->execute();
|
||||
|
||||
$this->operations[$class] = [];
|
||||
$this->operations[$scopeContext->getHash()] = [];
|
||||
while ($row = $result->fetch()) {
|
||||
$this->operations[$class][] = $row;
|
||||
if(!isset($this->operations[$scopeContext->getHash()][$row['class']])) {
|
||||
$this->operations[$scopeContext->getHash()][$row['class']] = [];
|
||||
}
|
||||
$this->operations[$scopeContext->getHash()][$row['class']][] = $row;
|
||||
}
|
||||
$result->closeCursor();
|
||||
|
||||
return $this->operations[$class];
|
||||
return $this->operations[$scopeContext->getHash()];
|
||||
}
|
||||
|
||||
public function getOperations(string $class, ScopeContext $scopeContext): array {
|
||||
if (!isset($this->operations[$scopeContext->getHash()])) {
|
||||
$this->getAllOperations($scopeContext);
|
||||
}
|
||||
return $this->operations[$scopeContext->getHash()][$class] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -173,22 +210,14 @@ class Manager implements IManager {
|
|||
throw new \UnexpectedValueException($this->l->t('Operation #%s does not exist', [$id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
$this->validateOperation($class, $name, $checks, $operation);
|
||||
|
||||
$checkIds = [];
|
||||
foreach ($checks as $check) {
|
||||
$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
|
||||
}
|
||||
|
||||
protected function insertOperation(
|
||||
string $class,
|
||||
string $name,
|
||||
array $checkIds,
|
||||
string $operation,
|
||||
string $entity,
|
||||
array $events
|
||||
): int {
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$query->insert('flow_operations')
|
||||
->values([
|
||||
|
@ -196,13 +225,81 @@ class Manager implements IManager {
|
|||
'name' => $query->createNamedParameter($name),
|
||||
'checks' => $query->createNamedParameter(json_encode(array_unique($checkIds))),
|
||||
'operation' => $query->createNamedParameter($operation),
|
||||
'entity' => $query->createNamedParameter($entity),
|
||||
'events' => $query->createNamedParameter(json_encode($events))
|
||||
]);
|
||||
$query->execute();
|
||||
|
||||
$id = $query->getLastInsertId();
|
||||
return $query->getLastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $name
|
||||
* @param array[] $checks
|
||||
* @param string $operation
|
||||
* @return array The added operation
|
||||
* @throws \UnexpectedValueException
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function addOperation(
|
||||
string $class,
|
||||
string $name,
|
||||
array $checks,
|
||||
string $operation,
|
||||
ScopeContext $scope,
|
||||
string $entity,
|
||||
array $events
|
||||
) {
|
||||
$this->validateOperation($class, $name, $checks, $operation, $entity, $events);
|
||||
|
||||
$this->connection->beginTransaction();
|
||||
|
||||
try {
|
||||
$checkIds = [];
|
||||
foreach ($checks as $check) {
|
||||
$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
|
||||
}
|
||||
|
||||
$id = $this->insertOperation($class, $name, $checkIds, $operation, $entity, $events);
|
||||
$this->addScope($id, $scope);
|
||||
|
||||
$this->connection->commit();
|
||||
} catch (DBALException $e) {
|
||||
$this->connection->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $this->getOperation($id);
|
||||
}
|
||||
|
||||
protected function canModify(int $id, ScopeContext $scopeContext):bool {
|
||||
if(isset($this->operationsByScope[$scopeContext->getHash()])) {
|
||||
return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
|
||||
}
|
||||
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb = $qb->select('o.id')
|
||||
->from('flow_operations', 'o')
|
||||
->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
|
||||
->where($qb->expr()->eq('s.type', $qb->createParameter('scope')));
|
||||
|
||||
if($scopeContext->getScope() !== IManager::SCOPE_ADMIN) {
|
||||
$qb->where($qb->expr()->eq('s.value', $qb->createParameter('scopeId')));
|
||||
}
|
||||
|
||||
$qb->setParameters(['scope' => $scopeContext->getScope(), 'scopeId' => $scopeContext->getScopeId()]);
|
||||
$result = $qb->execute();
|
||||
|
||||
$this->operationsByScope[$scopeContext->getHash()] = [];
|
||||
while($opId = $result->fetchColumn(0)) {
|
||||
$this->operationsByScope[$scopeContext->getHash()][] = (int)$opId;
|
||||
}
|
||||
$result->closeCursor();
|
||||
|
||||
return in_array($id, $this->operationsByScope[$scopeContext->getHash()], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string $name
|
||||
|
@ -210,23 +307,46 @@ class Manager implements IManager {
|
|||
* @param string $operation
|
||||
* @return array The updated operation
|
||||
* @throws \UnexpectedValueException
|
||||
* @throws \DomainException
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function updateOperation($id, $name, array $checks, $operation) {
|
||||
public function updateOperation(
|
||||
int $id,
|
||||
string $name,
|
||||
array $checks,
|
||||
string $operation,
|
||||
ScopeContext $scopeContext,
|
||||
string $entity,
|
||||
array $events
|
||||
): array {
|
||||
if(!$this->canModify($id, $scopeContext)) {
|
||||
throw new \DomainException('Target operation not within scope');
|
||||
};
|
||||
$row = $this->getOperation($id);
|
||||
$this->validateOperation($row['class'], $name, $checks, $operation);
|
||||
$this->validateOperation($row['class'], $name, $checks, $operation, $entity, $events);
|
||||
|
||||
$checkIds = [];
|
||||
foreach ($checks as $check) {
|
||||
$checkIds[] = $this->addCheck($check['class'], $check['operator'], $check['value']);
|
||||
}
|
||||
try {
|
||||
$this->connection->beginTransaction();
|
||||
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();
|
||||
$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))
|
||||
->set('entity', $query->createNamedParameter($entity))
|
||||
->set('events', $query->createNamedParameter(json_encode($events)))
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($id)));
|
||||
$query->execute();
|
||||
$this->connection->commit();
|
||||
} catch (DBALException $e) {
|
||||
$this->connection->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
unset($this->operations[$scopeContext->getHash()]);
|
||||
|
||||
return $this->getOperation($id);
|
||||
}
|
||||
|
@ -235,12 +355,67 @@ class Manager implements IManager {
|
|||
* @param int $id
|
||||
* @return bool
|
||||
* @throws \UnexpectedValueException
|
||||
* @throws DBALException
|
||||
* @throws \DomainException
|
||||
*/
|
||||
public function deleteOperation($id) {
|
||||
public function deleteOperation($id, ScopeContext $scopeContext) {
|
||||
if(!$this->canModify($id, $scopeContext)) {
|
||||
throw new \DomainException('Target operation not within scope');
|
||||
};
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$query->delete('flow_operations')
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($id)));
|
||||
return (bool) $query->execute();
|
||||
try {
|
||||
$this->connection->beginTransaction();
|
||||
$result = (bool)$query->delete('flow_operations')
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($id)))
|
||||
->execute();
|
||||
if($result) {
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$result &= (bool)$qb->delete('flow_operations_scope')
|
||||
->where($qb->expr()->eq('operation_id', $qb->createNamedParameter($id)))
|
||||
->execute();
|
||||
}
|
||||
$this->connection->commit();
|
||||
} catch (DBALException $e) {
|
||||
$this->connection->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if(isset($this->operations[$scopeContext->getHash()])) {
|
||||
unset($this->operations[$scopeContext->getHash()]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function validateEvents(string $entity, array $events, IOperation $operation) {
|
||||
try {
|
||||
/** @var IEntity $instance */
|
||||
$instance = $this->container->query($entity);
|
||||
} catch (QueryException $e) {
|
||||
throw new \UnexpectedValueException($this->l->t('Entity %s does not exist', [$entity]));
|
||||
}
|
||||
|
||||
if(!$instance instanceof IEntity) {
|
||||
throw new \UnexpectedValueException($this->l->t('Entity %s is invalid', [$entity]));
|
||||
}
|
||||
|
||||
if(empty($events)) {
|
||||
if(!$operation instanceof IComplexOperation) {
|
||||
throw new \UnexpectedValueException($this->l->t('No events are chosen.'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$availableEvents = [];
|
||||
foreach ($instance->getEvents() as $event) {
|
||||
/** @var IEntityEvent $event */
|
||||
$availableEvents[] = $event->getEventName();
|
||||
}
|
||||
|
||||
$diff = array_diff($events, $availableEvents);
|
||||
if(!empty($diff)) {
|
||||
throw new \UnexpectedValueException($this->l->t('Entity %s has no event %s', [$entity, array_shift($diff)]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -250,7 +425,7 @@ class Manager implements IManager {
|
|||
* @param string $operation
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
protected function validateOperation($class, $name, array $checks, $operation) {
|
||||
public function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) {
|
||||
try {
|
||||
/** @var IOperation $instance */
|
||||
$instance = $this->container->query($class);
|
||||
|
@ -262,6 +437,8 @@ class Manager implements IManager {
|
|||
throw new \UnexpectedValueException($this->l->t('Operation %s is invalid', [$class]));
|
||||
}
|
||||
|
||||
$this->validateEvents($entity, $events, $instance);
|
||||
|
||||
$instance->validateOperation($name, $checks, $operation);
|
||||
|
||||
foreach ($checks as $check) {
|
||||
|
@ -276,6 +453,12 @@ class Manager implements IManager {
|
|||
throw new \UnexpectedValueException($this->l->t('Check %s is invalid', [$class]));
|
||||
}
|
||||
|
||||
if (!empty($instance->supportedEntities())
|
||||
&& !in_array($entity, $instance->supportedEntities())
|
||||
) {
|
||||
throw new \UnexpectedValueException($this->l->t('Check %s is not allowed with this entity', [$class]));
|
||||
}
|
||||
|
||||
$instance->validateCheck($check['operator'], $check['value']);
|
||||
}
|
||||
}
|
||||
|
@ -353,4 +536,121 @@ class Manager implements IManager {
|
|||
|
||||
return $query->getLastInsertId();
|
||||
}
|
||||
|
||||
protected function addScope(int $operationId, ScopeContext $scope): void {
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
|
||||
$insertQuery = $query->insert('flow_operations_scope');
|
||||
$insertQuery->values([
|
||||
'operation_id' => $query->createNamedParameter($operationId),
|
||||
'type' => $query->createNamedParameter($scope->getScope()),
|
||||
'value' => $query->createNamedParameter($scope->getScopeId()),
|
||||
]);
|
||||
$insertQuery->execute();
|
||||
}
|
||||
|
||||
public function formatOperation(array $operation): array {
|
||||
$checkIds = json_decode($operation['checks'], true);
|
||||
$checks = $this->getChecks($checkIds);
|
||||
|
||||
$operation['checks'] = [];
|
||||
foreach ($checks as $check) {
|
||||
// Remove internal values
|
||||
unset($check['id']);
|
||||
unset($check['hash']);
|
||||
|
||||
$operation['checks'][] = $check;
|
||||
}
|
||||
|
||||
return $operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IEntity[]
|
||||
*/
|
||||
public function getEntitiesList(): array {
|
||||
$this->eventDispatcher->dispatch(IManager::EVENT_NAME_REG_ENTITY, new GenericEvent($this));
|
||||
|
||||
return array_merge($this->getBuildInEntities(), $this->registeredEntities);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IOperation[]
|
||||
*/
|
||||
public function getOperatorList(): array {
|
||||
$this->eventDispatcher->dispatch(IManager::EVENT_NAME_REG_OPERATION, new GenericEvent($this));
|
||||
|
||||
return array_merge($this->getBuildInOperators(), $this->registeredOperators);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ICheck[]
|
||||
*/
|
||||
public function getCheckList(): array {
|
||||
$this->eventDispatcher->dispatch(IManager::EVENT_NAME_REG_CHECK, new GenericEvent($this));
|
||||
|
||||
return array_merge($this->getBuildInChecks(), $this->registeredChecks);
|
||||
}
|
||||
|
||||
public function registerEntity(IEntity $entity): void {
|
||||
$this->registeredEntities[get_class($entity)] = $entity;
|
||||
}
|
||||
|
||||
public function registerOperation(IOperation $operator): void {
|
||||
$this->registeredOperators[get_class($operator)] = $operator;
|
||||
}
|
||||
|
||||
public function registerCheck(ICheck $check): void {
|
||||
$this->registeredChecks[get_class($check)] = $check;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IEntity[]
|
||||
*/
|
||||
protected function getBuildInEntities(): array {
|
||||
try {
|
||||
return [
|
||||
$this->container->query(File::class),
|
||||
];
|
||||
} catch (QueryException $e) {
|
||||
$this->logger->logException($e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IOperation[]
|
||||
*/
|
||||
protected function getBuildInOperators(): array {
|
||||
try {
|
||||
return [
|
||||
// None yet
|
||||
];
|
||||
} catch (QueryException $e) {
|
||||
$this->logger->logException($e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IEntity[]
|
||||
*/
|
||||
protected function getBuildInChecks(): array {
|
||||
try {
|
||||
return [
|
||||
$this->container->query(FileMimeType::class),
|
||||
$this->container->query(FileName::class),
|
||||
$this->container->query(FileSize::class),
|
||||
$this->container->query(FileSystemTags::class),
|
||||
$this->container->query(RequestRemoteAddress::class),
|
||||
$this->container->query(RequestTime::class),
|
||||
$this->container->query(RequestURL::class),
|
||||
$this->container->query(RequestUserAgent::class),
|
||||
$this->container->query(UserGroupMembership::class),
|
||||
];
|
||||
} catch (QueryException $e) {
|
||||
$this->logger->logException($e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Migration;
|
||||
|
||||
use Doctrine\DBAL\Driver\Statement;
|
||||
use OCA\WorkflowEngine\Entity\File;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\IRepairStep;
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
|
||||
class PopulateNewlyIntroducedDatabaseFields implements IRepairStep {
|
||||
|
||||
/** @var IDBConnection */
|
||||
private $dbc;
|
||||
|
||||
public function __construct(IDBConnection $dbc) {
|
||||
$this->dbc = $dbc;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return 'Populating added database structures for workflows';
|
||||
}
|
||||
|
||||
public function run(IOutput $output) {
|
||||
$result = $this->getIdsWithoutScope();
|
||||
|
||||
$this->populateScopeTable($result);
|
||||
|
||||
$result->closeCursor();
|
||||
|
||||
$this->populateEntityCol();
|
||||
}
|
||||
|
||||
protected function populateEntityCol() {
|
||||
$qb = $this->dbc->getQueryBuilder();
|
||||
|
||||
$qb->update('flow_operations')
|
||||
->set('entity', $qb->createNamedParameter(File::class))
|
||||
->where($qb->expr()->emptyString('entity'))
|
||||
->execute();
|
||||
|
||||
}
|
||||
|
||||
protected function populateScopeTable(Statement $ids): void {
|
||||
$qb = $this->dbc->getQueryBuilder();
|
||||
|
||||
$insertQuery = $qb->insert('flow_operations_scope');
|
||||
while($id = $ids->fetchColumn(0)) {
|
||||
$insertQuery->values(['operation_id' => $qb->createNamedParameter($id), 'type' => IManager::SCOPE_ADMIN]);
|
||||
$insertQuery->execute();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getIdsWithoutScope(): Statement {
|
||||
$qb = $this->dbc->getQueryBuilder();
|
||||
$selectQuery = $qb->select('o.id')
|
||||
->from('flow_operations', 'o')
|
||||
->leftJoin('o', 'flow_operations_scope', 's', $qb->expr()->eq('o.id', 's.operation_id'))
|
||||
->where($qb->expr()->isNull('s.operation_id'));
|
||||
// The left join operation is not necessary, usually, but it's a safe-guard
|
||||
// in case the repair step is executed multiple times for whatever reason.
|
||||
|
||||
return $selectQuery->execute();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\WorkflowEngine\Migration;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
use OCP\Migration\IOutput;
|
||||
|
||||
class Version2019Date20190808074233 extends SimpleMigrationStep {
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
if (!$schema->hasTable('flow_checks')) {
|
||||
$table = $schema->createTable('flow_checks');
|
||||
$table->addColumn('id', Type::INTEGER, [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
'length' => 4,
|
||||
]);
|
||||
$table->addColumn('class', Type::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 256,
|
||||
]);
|
||||
$table->addColumn('operator', Type::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 16,
|
||||
]);
|
||||
$table->addColumn('value', Type::TEXT, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
$table->addColumn('hash', Type::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 32,
|
||||
]);
|
||||
$table->setPrimaryKey(['id']);
|
||||
$table->addUniqueIndex(['hash'], 'flow_unique_hash');
|
||||
}
|
||||
|
||||
if (!$schema->hasTable('flow_operations')) {
|
||||
$table = $schema->createTable('flow_operations');
|
||||
$table->addColumn('id', Type::INTEGER, [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
'length' => 4,
|
||||
]);
|
||||
$table->addColumn('class', Type::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 256,
|
||||
]);
|
||||
$table->addColumn('name', Type::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 256,
|
||||
]);
|
||||
$table->addColumn('checks', Type::TEXT, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
$table->addColumn('operation', Type::TEXT, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
$this->ensureEntityColumns($table);
|
||||
$table->setPrimaryKey(['id']);
|
||||
} else {
|
||||
$table = $schema->getTable('flow_operations');
|
||||
$this->ensureEntityColumns($table);
|
||||
}
|
||||
|
||||
if (!$schema->hasTable('flow_operations_scope')) {
|
||||
$table = $schema->createTable('flow_operations_scope');
|
||||
$table->addColumn('id', Type::BIGINT, [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
'length' => 4,
|
||||
]);
|
||||
$table->addColumn('operation_id', Type::INTEGER, [
|
||||
'notnull' => true,
|
||||
'length' => 4,
|
||||
]);
|
||||
$table->addColumn('type', Type::INTEGER, [
|
||||
'notnull' => true,
|
||||
'length' => 4,
|
||||
]);
|
||||
$table->addColumn('value', Type::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 64,
|
||||
]);
|
||||
$table->setPrimaryKey(['id']);
|
||||
$table->addUniqueIndex(['operation_id', 'type', 'value'], 'flow_unique_scope');
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
protected function ensureEntityColumns(Table $table) {
|
||||
if(!$table->hasColumn('entity')) {
|
||||
$table->addColumn('entity', Type::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 256,
|
||||
]);
|
||||
}
|
||||
if(!$table->hasColumn('events')) {
|
||||
$table->addColumn('events', Type::TEXT, [
|
||||
'notnull' => true,
|
||||
'default' => '[]',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Service;
|
||||
|
||||
use OCA\WorkflowEngine\AppInfo\Application;
|
||||
use OCA\WorkflowEngine\Entity\File;
|
||||
use OCA\WorkflowEngine\Helper\ScopeContext;
|
||||
use OCA\WorkflowEngine\Manager;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\Files\Node;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IL10N;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\IUserSession;
|
||||
use OCP\WorkflowEngine\ICheck;
|
||||
use OCP\WorkflowEngine\IEntity;
|
||||
use OCP\WorkflowEngine\IEntityCheck;
|
||||
use OCP\WorkflowEngine\IFileCheck;
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
use OCP\WorkflowEngine\IRuleMatcher;
|
||||
|
||||
class RuleMatcher implements IRuleMatcher {
|
||||
|
||||
/** @var IUserSession */
|
||||
protected $session;
|
||||
/** @var IManager */
|
||||
protected $manager;
|
||||
/** @var array */
|
||||
protected $contexts;
|
||||
/** @var IServerContainer */
|
||||
protected $container;
|
||||
/** @var array */
|
||||
protected $fileInfo = [];
|
||||
/** @var IL10N */
|
||||
protected $l;
|
||||
|
||||
public function __construct(IUserSession $session, IServerContainer $container, IL10N $l, Manager $manager) {
|
||||
$this->session = $session;
|
||||
$this->manager = $manager;
|
||||
$this->container = $container;
|
||||
$this->l = $l;
|
||||
}
|
||||
|
||||
public function setFileInfo(IStorage $storage, string $path): void {
|
||||
$this->fileInfo['storage'] = $storage;
|
||||
$this->fileInfo['path'] = $path;
|
||||
}
|
||||
|
||||
|
||||
public function setEntitySubject(IEntity $entity, $subject): void {
|
||||
$this->contexts[get_class($entity)] = [$entity, $subject];
|
||||
}
|
||||
|
||||
public function getMatchingOperations(string $class, bool $returnFirstMatchingOperationOnly = true): array {
|
||||
$scopes[] = new ScopeContext(IManager::SCOPE_ADMIN);
|
||||
$user = $this->session->getUser();
|
||||
if($user !== null) {
|
||||
$scopes[] = new ScopeContext(IManager::SCOPE_USER, $user->getUID());
|
||||
}
|
||||
|
||||
$operations = [];
|
||||
foreach ($scopes as $scope) {
|
||||
$operations = array_merge($operations, $this->manager->getOperations($class, $scope));
|
||||
}
|
||||
|
||||
$matches = [];
|
||||
foreach ($operations as $operation) {
|
||||
$checkIds = json_decode($operation['checks'], true);
|
||||
$checks = $this->manager->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
|
||||
*/
|
||||
public 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 IFileCheck) {
|
||||
if (empty($this->fileInfo)) {
|
||||
throw new \RuntimeException('Must set file info before running the check');
|
||||
}
|
||||
$checkInstance->setFileInfo($this->fileInfo['storage'], $this->fileInfo['path']);
|
||||
} elseif ($checkInstance instanceof IEntityCheck) {
|
||||
foreach($this->contexts as $entityInfo) {
|
||||
list($entity, $subject) = $entityInfo;
|
||||
$checkInstance->setEntitySubject($entity, $subject);
|
||||
}
|
||||
} else {
|
||||
// Check is invalid
|
||||
throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class']));
|
||||
}
|
||||
return $checkInstance->executeCheck($check['operator'], $check['value']);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Settings;
|
||||
|
||||
use OCA\WorkflowEngine\AppInfo\Application;
|
||||
use OCA\WorkflowEngine\Manager;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IL10N;
|
||||
use OCP\Settings\ISettings;
|
||||
use OCP\WorkflowEngine\ICheck;
|
||||
use OCP\WorkflowEngine\IComplexOperation;
|
||||
use OCP\WorkflowEngine\IEntity;
|
||||
use OCP\WorkflowEngine\IEntityEvent;
|
||||
use OCP\WorkflowEngine\IOperation;
|
||||
use OCP\WorkflowEngine\ISpecificOperation;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
abstract class ASettings implements ISettings {
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
|
||||
/** @var string */
|
||||
private $appName;
|
||||
|
||||
/** @var EventDispatcherInterface */
|
||||
private $eventDispatcher;
|
||||
|
||||
/** @var Manager */
|
||||
private $manager;
|
||||
|
||||
/** @var IInitialStateService */
|
||||
private $initialStateService;
|
||||
|
||||
/**
|
||||
* @param string $appName
|
||||
* @param IL10N $l
|
||||
* @param EventDispatcherInterface $eventDispatcher
|
||||
*/
|
||||
public function __construct(
|
||||
$appName,
|
||||
IL10N $l,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
Manager $manager,
|
||||
IInitialStateService $initialStateService
|
||||
) {
|
||||
$this->appName = $appName;
|
||||
$this->l10n = $l;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->manager = $manager;
|
||||
$this->initialStateService = $initialStateService;
|
||||
}
|
||||
|
||||
abstract function getScope(): int;
|
||||
|
||||
/**
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm() {
|
||||
$this->eventDispatcher->dispatch('OCP\WorkflowEngine::loadAdditionalSettingScripts');
|
||||
|
||||
$entities = $this->manager->getEntitiesList();
|
||||
$this->initialStateService->provideInitialState(
|
||||
Application::APP_ID,
|
||||
'entities',
|
||||
$this->entitiesToArray($entities)
|
||||
);
|
||||
|
||||
$operators = $this->manager->getOperatorList();
|
||||
$this->initialStateService->provideInitialState(
|
||||
Application::APP_ID,
|
||||
'operators',
|
||||
$this->operatorsToArray($operators)
|
||||
);
|
||||
|
||||
$checks = $this->manager->getCheckList();
|
||||
$this->initialStateService->provideInitialState(
|
||||
Application::APP_ID,
|
||||
'checks',
|
||||
$this->checksToArray($checks)
|
||||
);
|
||||
|
||||
$this->initialStateService->provideInitialState(
|
||||
Application::APP_ID,
|
||||
'scope',
|
||||
$this->getScope()
|
||||
);
|
||||
|
||||
return new TemplateResponse(Application::APP_ID, 'settings', [], 'blank');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the section ID, e.g. 'sharing'
|
||||
*/
|
||||
public function getSection() {
|
||||
return 'workflow';
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 0;
|
||||
}
|
||||
|
||||
private function entitiesToArray(array $entities) {
|
||||
return array_map(function (IEntity $entity) {
|
||||
$events = array_map(function(IEntityEvent $entityEvent) {
|
||||
return [
|
||||
'eventName' => $entityEvent->getEventName(),
|
||||
'displayName' => $entityEvent->getDisplayName()
|
||||
];
|
||||
}, $entity->getEvents());
|
||||
|
||||
return [
|
||||
'id' => get_class($entity),
|
||||
'icon' => $entity->getIcon(),
|
||||
'name' => $entity->getName(),
|
||||
'events' => $events,
|
||||
];
|
||||
}, $entities);
|
||||
}
|
||||
|
||||
private function operatorsToArray(array $operators) {
|
||||
$operators = array_filter($operators, function(IOperation $operator) {
|
||||
return $operator->isAvailableForScope($this->getScope());
|
||||
});
|
||||
|
||||
return array_map(function (IOperation $operator) {
|
||||
return [
|
||||
'id' => get_class($operator),
|
||||
'icon' => $operator->getIcon(),
|
||||
'name' => $operator->getDisplayName(),
|
||||
'description' => $operator->getDescription(),
|
||||
'fixedEntity' => $operator instanceof ISpecificOperation ? $operator->getEntityId() : '',
|
||||
'isComplex' => $operator instanceof IComplexOperation,
|
||||
'triggerHint' => $operator instanceof IComplexOperation ? $operator->getTriggerHint() : '',
|
||||
];
|
||||
}, $operators);
|
||||
}
|
||||
|
||||
private function checksToArray(array $checks) {
|
||||
$checks = array_filter($checks, function(ICheck $check) {
|
||||
return $check->isAvailableForScope($this->getScope());
|
||||
});
|
||||
|
||||
return array_map(function (ICheck $check) {
|
||||
return [
|
||||
'id' => get_class($check),
|
||||
'supportedEntities' => $check->supportedEntities(),
|
||||
];
|
||||
}, $checks);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Settings;
|
||||
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
|
||||
class Admin extends ASettings {
|
||||
|
||||
function getScope(): int {
|
||||
return IManager::SCOPE_ADMIN;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\Settings;
|
||||
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
|
||||
class Personal extends ASettings {
|
||||
|
||||
function getScope(): int {
|
||||
return IManager::SCOPE_USER;
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@ class Section implements IIconSection {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->l->t('Tag management');
|
||||
return $this->l->t('Workflows');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,385 +0,0 @@
|
|||
/**
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import OperationTemplate from './templates/operation.handlebars';
|
||||
import OperationsTemplate from './templates/operations.handlebars';
|
||||
|
||||
(function() {
|
||||
OCA.WorkflowEngine = _.extend(OCA.WorkflowEngine || {}, {
|
||||
availablePlugins: [],
|
||||
availableChecks: [],
|
||||
|
||||
getCheckByClass: function(className) {
|
||||
var length = OCA.WorkflowEngine.availableChecks.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (OCA.WorkflowEngine.availableChecks[i]['class'] === className) {
|
||||
return OCA.WorkflowEngine.availableChecks[i];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 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': ''
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* .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')
|
||||
});
|
||||
|
||||
/**
|
||||
* 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.OperationView
|
||||
*
|
||||
* this creates the view for a single operation
|
||||
*/
|
||||
OCA.WorkflowEngine.OperationView =
|
||||
OC.Backbone.View.extend({
|
||||
templateId: '#operation-template',
|
||||
events: {
|
||||
'change .check-class': 'checkChanged',
|
||||
'change .check-operator': 'checkChanged',
|
||||
'change .check-value': 'checkChanged',
|
||||
'change .operation-name': 'operationChanged',
|
||||
'change .operation-operation': '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,
|
||||
groups: [],
|
||||
template: function(vars) {
|
||||
return OperationTemplate(_.extend(
|
||||
{
|
||||
shortRuleDescTXT: t('workflowengine', 'Short rule description'),
|
||||
addRuleTXT: t('workflowengine', 'Add rule'),
|
||||
resetTXT: t('workflowengine', 'Reset'),
|
||||
saveTXT: t('workflowengine', 'Save'),
|
||||
savingTXT: t('workflowengine', 'Saving…')
|
||||
},
|
||||
vars
|
||||
));
|
||||
},
|
||||
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;
|
||||
}
|
||||
var self = this;
|
||||
$.ajax({
|
||||
url: OC.linkToOCS('cloud/groups', 2) + 'details',
|
||||
dataType: 'json',
|
||||
quietMillis: 100,
|
||||
}).success(function(data) {
|
||||
if (data.ocs.data.groups && data.ocs.data.groups.length > 0) {
|
||||
|
||||
data.ocs.data.groups.forEach(function(group) {
|
||||
self.groups.push({ id: group.id, displayname: group.displayname });
|
||||
});
|
||||
self.render();
|
||||
|
||||
} else {
|
||||
OC.Notification.error(t('workflowengine', 'Group list is empty'), { type: 'error' });
|
||||
console.log(data);
|
||||
}
|
||||
}).error(function(data) {
|
||||
OC.Notification.error(t('workflowengine', 'Unable to retrieve the group list'), { type: 'error' });
|
||||
console.log(data);
|
||||
});
|
||||
},
|
||||
delete: function() {
|
||||
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
|
||||
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.delete, this));
|
||||
return;
|
||||
}
|
||||
|
||||
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() {
|
||||
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
|
||||
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.save, this));
|
||||
return;
|
||||
}
|
||||
|
||||
var success = function(model, response, options) {
|
||||
this.saving = false;
|
||||
this.originalModel = JSON.parse(JSON.stringify(this.model));
|
||||
|
||||
this.message = t('workflowengine', '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[0]['class'],
|
||||
operators = OCA.WorkflowEngine.availableChecks[0]['operators'];
|
||||
|
||||
checks.push({
|
||||
'class': classname,
|
||||
'operator': operators[0]['operator'],
|
||||
'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 check = OCA.WorkflowEngine.getCheckByClass(value);
|
||||
if (!_.isUndefined(check)) {
|
||||
checks[id]['operator'] = check['operators'][0]['operator'];
|
||||
checks[id]['value'] = '';
|
||||
}
|
||||
}
|
||||
// model change will trigger render
|
||||
this.model.set({ 'checks': checks });
|
||||
},
|
||||
deleteCheck: function(event) {
|
||||
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' && key !== 'operation') {
|
||||
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,
|
||||
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();
|
||||
var self = this;
|
||||
|
||||
_.each(OCA.WorkflowEngine.availablePlugins, function(plugin) {
|
||||
if (_.isFunction(plugin.render)) {
|
||||
plugin.render(valueElement, check, self.groups);
|
||||
}
|
||||
});
|
||||
}, 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 = '';
|
||||
}
|
||||
|
||||
return this.$el;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @class OCA.WorkflowEngine.OperationsView
|
||||
*
|
||||
* this creates the view for configured operations
|
||||
*/
|
||||
OCA.WorkflowEngine.OperationsView =
|
||||
OC.Backbone.View.extend({
|
||||
templateId: '#operations-template',
|
||||
collection: null,
|
||||
$el: null,
|
||||
events: {
|
||||
'click .button-add-operation': 'add'
|
||||
},
|
||||
template: function(vars) {
|
||||
return OperationsTemplate(_.extend(
|
||||
{
|
||||
addRuleGroupTXT: t('workflowengine', 'Add rule group')
|
||||
},
|
||||
vars
|
||||
));
|
||||
},
|
||||
initialize: function(classname) {
|
||||
if (!OCA.WorkflowEngine.availablePlugins.length) {
|
||||
OCA.WorkflowEngine.availablePlugins = OC.Plugins.getPlugins('OCA.WorkflowEngine.CheckPlugins');
|
||||
_.each(OCA.WorkflowEngine.availablePlugins, function(plugin) {
|
||||
if (_.isFunction(plugin.getCheck)) {
|
||||
OCA.WorkflowEngine.availableChecks.push(plugin.getCheck(classname));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.collection.fetch({
|
||||
data: {
|
||||
'class': classname
|
||||
}
|
||||
});
|
||||
this.collection.once('sync', this.render, this);
|
||||
},
|
||||
add: function() {
|
||||
var operation = this.collection.create();
|
||||
this.renderOperation(operation);
|
||||
},
|
||||
renderOperation: function(subView) {
|
||||
var operationsElement = this.$el.find('.operations');
|
||||
operationsElement.append(subView.$el);
|
||||
subView.render();
|
||||
},
|
||||
render: function() {
|
||||
this.$el.html(this.template());
|
||||
this.collection.each(this.renderOperation, this);
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div v-click-outside="hideDelete" class="check" @click="showDelete">
|
||||
<Multiselect ref="checkSelector" v-model="currentOption" :options="options"
|
||||
label="name" track-by="class" :allow-empty="false"
|
||||
:placeholder="t('workflowengine', 'Select a filter')" @input="updateCheck" />
|
||||
<Multiselect v-model="currentOperator" :disabled="!currentOption" :options="operators"
|
||||
label="name" track-by="operator" :allow-empty="false"
|
||||
:placeholder="t('workflowengine', 'Select a comparator')" @input="updateCheck" />
|
||||
<component :is="currentOption.component" v-if="currentOperator && currentComponent" v-model="check.value"
|
||||
:disabled="!currentOption" :check="check"
|
||||
@input="updateCheck"
|
||||
@valid="(valid=true) && validate()"
|
||||
@invalid="(valid=false) && validate()" />
|
||||
<input v-else v-model="check.value" type="text"
|
||||
:class="{ invalid: !valid }"
|
||||
:disabled="!currentOption" :placeholder="valuePlaceholder" @input="updateCheck">
|
||||
<Actions v-if="deleteVisible || !currentOption">
|
||||
<ActionButton icon="icon-delete" @click="$emit('remove')" />
|
||||
</Actions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
|
||||
import { Actions } from 'nextcloud-vue/dist/Components/Actions'
|
||||
import { ActionButton } from 'nextcloud-vue/dist/Components/ActionButton'
|
||||
import ClickOutside from 'vue-click-outside'
|
||||
|
||||
export default {
|
||||
name: 'Check',
|
||||
components: {
|
||||
ActionButton,
|
||||
Actions,
|
||||
Multiselect
|
||||
},
|
||||
directives: {
|
||||
ClickOutside
|
||||
},
|
||||
props: {
|
||||
check: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
rule: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
deleteVisible: false,
|
||||
currentOption: null,
|
||||
currentOperator: null,
|
||||
options: [],
|
||||
valid: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
Checks() {
|
||||
return this.$store.getters.getChecksForEntity(this.rule.entity)
|
||||
},
|
||||
operators() {
|
||||
if (!this.currentOption) { return [] }
|
||||
return this.Checks[this.currentOption.class].operators
|
||||
},
|
||||
currentComponent() {
|
||||
if (!this.currentOption) { return [] }
|
||||
const currentComponent = this.Checks[this.currentOption.class].component
|
||||
return currentComponent
|
||||
},
|
||||
valuePlaceholder() {
|
||||
if (this.currentOption && this.currentOption.placeholder) {
|
||||
return this.currentOption.placeholder(this.check)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'check.operator': function() {
|
||||
this.validate()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.options = Object.values(this.Checks)
|
||||
this.currentOption = this.Checks[this.check.class]
|
||||
this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator)
|
||||
},
|
||||
methods: {
|
||||
showDelete() {
|
||||
this.deleteVisible = true
|
||||
},
|
||||
hideDelete() {
|
||||
this.deleteVisible = false
|
||||
},
|
||||
validate() {
|
||||
if (this.currentOption && this.currentOption.validate) {
|
||||
if (this.currentOption.validate(this.check)) {
|
||||
this.valid = true
|
||||
} else {
|
||||
this.valid = false
|
||||
}
|
||||
}
|
||||
this.$store.dispatch('setValid', { rule: this.rule, valid: this.rule.valid && this.valid })
|
||||
return this.valid
|
||||
},
|
||||
updateCheck() {
|
||||
if (this.check.class !== this.currentOption.class) {
|
||||
this.currentOperator = this.operators[0]
|
||||
}
|
||||
this.check.class = this.currentOption.class
|
||||
this.check.operator = this.currentOperator.operator
|
||||
|
||||
if (!this.validate()) {
|
||||
return
|
||||
}
|
||||
this.$emit('update', this.check)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.check {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
padding-right: 20px;
|
||||
& > *:not(.icon-delete) {
|
||||
width: 180px;
|
||||
}
|
||||
& > .multiselect,
|
||||
& > input[type=text] {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
input[type=text] {
|
||||
margin: 0;
|
||||
}
|
||||
::placeholder {
|
||||
font-size: 10px;
|
||||
}
|
||||
.icon-delete {
|
||||
margin-top: -5px;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
button.action-item.action-item--single.icon-delete {
|
||||
height: 34px;
|
||||
width: 34px;
|
||||
}
|
||||
.invalid {
|
||||
border: 1px solid var(--color-error) !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<div>
|
||||
<Multiselect
|
||||
:value="currentValue"
|
||||
:placeholder="t('workflowengine', 'Select a file type')"
|
||||
label="label"
|
||||
track-by="pattern"
|
||||
:options="options" :multiple="false" :tagging="false"
|
||||
@input="setValue">
|
||||
<template slot="singleLabel" slot-scope="props">
|
||||
<span class="option__icon" :class="props.option.icon" />
|
||||
<span class="option__title option__title_single">{{ props.option.label }}</span>
|
||||
</template>
|
||||
<template slot="option" slot-scope="props">
|
||||
<span class="option__icon" :class="props.option.icon" />
|
||||
<span class="option__title">{{ props.option.label }}</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
<input v-if="!isPredefined" type="text" :value="currentValue.pattern"
|
||||
@input="updateCustom">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
|
||||
import valueMixin from './../../mixins/valueMixin'
|
||||
|
||||
export default {
|
||||
name: 'FileMimeType',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
mixins: [
|
||||
valueMixin
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
predefinedTypes: [
|
||||
{
|
||||
icon: 'icon-picture',
|
||||
label: t('workflowengine', 'Images'),
|
||||
pattern: '/image\\/.*/'
|
||||
},
|
||||
{
|
||||
icon: 'icon-category-office',
|
||||
label: t('workflowengine', 'Office documents'),
|
||||
pattern: '/(vnd\\.(ms-|openxmlformats-).*))$/'
|
||||
},
|
||||
{
|
||||
icon: 'icon-filetype-file',
|
||||
label: t('workflowengine', 'PDF documents'),
|
||||
pattern: 'application/pdf'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
return [...this.predefinedTypes, this.customValue]
|
||||
},
|
||||
isPredefined() {
|
||||
const matchingPredefined = this.predefinedTypes.find((type) => this.newValue === type.pattern)
|
||||
if (matchingPredefined) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
customValue() {
|
||||
return {
|
||||
icon: 'icon-settings-dark',
|
||||
label: t('workflowengine', 'Custom mimetype'),
|
||||
pattern: ''
|
||||
}
|
||||
},
|
||||
currentValue() {
|
||||
const matchingPredefined = this.predefinedTypes.find((type) => this.newValue === type.pattern)
|
||||
if (matchingPredefined) {
|
||||
return matchingPredefined
|
||||
}
|
||||
return {
|
||||
icon: 'icon-settings-dark',
|
||||
label: t('workflowengine', 'Custom mimetype'),
|
||||
pattern: this.newValue
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateRegex(string) {
|
||||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/
|
||||
var result = regexRegex.exec(string)
|
||||
return result !== null
|
||||
},
|
||||
setValue(value) {
|
||||
// TODO: check if value requires a regex and set the check operator according to that
|
||||
if (value !== null) {
|
||||
this.newValue = value.pattern
|
||||
this.$emit('input', this.newValue)
|
||||
}
|
||||
},
|
||||
updateCustom(event) {
|
||||
this.newValue = event.target.value
|
||||
this.$emit('input', this.newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped src="./../../css/multiselect.css"></style>
|
|
@ -0,0 +1,73 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MultiselectTag v-model="newValue" :multiple="false"
|
||||
label="Select a tag"
|
||||
@input="update" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { MultiselectTag } from './MultiselectTag'
|
||||
|
||||
export default {
|
||||
name: 'FileSystemTag',
|
||||
components: {
|
||||
MultiselectTag
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newValue: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
this.updateValue()
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.updateValue()
|
||||
},
|
||||
methods: {
|
||||
updateValue() {
|
||||
if (this.value !== '') {
|
||||
this.newValue = this.value
|
||||
} else {
|
||||
this.newValue = null
|
||||
}
|
||||
},
|
||||
update() {
|
||||
this.$emit('input', this.newValue || '')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,127 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<Multiselect v-model="inputValObjects"
|
||||
:options="tags" :options-limit="5"
|
||||
:placeholder="label"
|
||||
track-by="id"
|
||||
:custom-label="tagLabel"
|
||||
class="multiselect-vue" :multiple="multiple"
|
||||
:close-on-select="false" :tag-width="60"
|
||||
:disabled="disabled" @input="update">
|
||||
<span slot="noResult">{{ t('core', 'No results') }}</span>
|
||||
<template #option="scope">
|
||||
{{ tagLabel(scope.option) }}
|
||||
</template>
|
||||
</multiselect>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
|
||||
import { searchTags } from './api'
|
||||
|
||||
let uuid = 0
|
||||
export default {
|
||||
name: 'MultiselectTag',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputValObjects: [],
|
||||
tags: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
id() {
|
||||
return 'settings-input-text-' + this.uuid
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.inputValObjects = this.getValueObject()
|
||||
}
|
||||
},
|
||||
beforeCreate: function() {
|
||||
this.uuid = uuid.toString()
|
||||
uuid += 1
|
||||
searchTags().then((result) => {
|
||||
this.tags = result
|
||||
this.inputValObjects = this.getValueObject()
|
||||
}).catch(console.error.bind(this))
|
||||
},
|
||||
methods: {
|
||||
getValueObject() {
|
||||
if (this.tags.length === 0) {
|
||||
return []
|
||||
}
|
||||
if (this.multiple) {
|
||||
return this.value.filter((tag) => tag !== '').map(
|
||||
(id) => this.tags.find((tag2) => tag2.id === id)
|
||||
)
|
||||
} else {
|
||||
return this.tags.find((tag) => tag.id === this.value)
|
||||
}
|
||||
},
|
||||
update() {
|
||||
if (this.multiple) {
|
||||
this.$emit('input', this.inputValObjects.map((element) => element.id))
|
||||
} else {
|
||||
if (this.inputValObjects === null) {
|
||||
this.$emit('input', '')
|
||||
} else {
|
||||
this.$emit('input', this.inputValObjects.id)
|
||||
}
|
||||
}
|
||||
},
|
||||
tagLabel({ displayName, userVisible, userAssignable }) {
|
||||
if (userVisible === false) {
|
||||
return t('systemtags', '%s (invisible)').replace('%s', displayName)
|
||||
}
|
||||
if (userAssignable === false) {
|
||||
return t('systemtags', '%s (restricted)').replace('%s', displayName)
|
||||
}
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,90 @@
|
|||
import axios from 'nextcloud-axios'
|
||||
import { generateRemoteUrl } from 'nextcloud-router'
|
||||
|
||||
const xmlToJson = (xml) => {
|
||||
let obj = {}
|
||||
|
||||
if (xml.nodeType === 1) {
|
||||
if (xml.attributes.length > 0) {
|
||||
obj['@attributes'] = {}
|
||||
for (let j = 0; j < xml.attributes.length; j++) {
|
||||
const attribute = xml.attributes.item(j)
|
||||
obj['@attributes'][attribute.nodeName] = attribute.nodeValue
|
||||
}
|
||||
}
|
||||
} else if (xml.nodeType === 3) {
|
||||
obj = xml.nodeValue
|
||||
}
|
||||
|
||||
if (xml.hasChildNodes()) {
|
||||
for (let i = 0; i < xml.childNodes.length; i++) {
|
||||
const item = xml.childNodes.item(i)
|
||||
const nodeName = item.nodeName
|
||||
if (typeof (obj[nodeName]) === 'undefined') {
|
||||
obj[nodeName] = xmlToJson(item)
|
||||
} else {
|
||||
if (typeof obj[nodeName].push === 'undefined') {
|
||||
var old = obj[nodeName]
|
||||
obj[nodeName] = []
|
||||
obj[nodeName].push(old)
|
||||
}
|
||||
obj[nodeName].push(xmlToJson(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
const parseXml = (xml) => {
|
||||
let dom = null
|
||||
try {
|
||||
dom = (new DOMParser()).parseFromString(xml, 'text/xml')
|
||||
} catch (e) {
|
||||
console.error('Failed to parse xml document', e)
|
||||
}
|
||||
return dom
|
||||
}
|
||||
|
||||
const xmlToTagList = (xml) => {
|
||||
const json = xmlToJson(parseXml(xml))
|
||||
const list = json['d:multistatus']['d:response']
|
||||
const result = []
|
||||
for (const index in list) {
|
||||
const tag = list[index]['d:propstat']
|
||||
|
||||
if (tag['d:status']['#text'] !== 'HTTP/1.1 200 OK') {
|
||||
continue
|
||||
}
|
||||
result.push({
|
||||
id: tag['d:prop']['oc:id']['#text'],
|
||||
displayName: tag['d:prop']['oc:display-name']['#text'],
|
||||
canAssign: tag['d:prop']['oc:can-assign']['#text'] === 'true',
|
||||
userAssignable: tag['d:prop']['oc:user-assignable']['#text'] === 'true',
|
||||
userVisible: tag['d:prop']['oc:user-visible']['#text'] === 'true'
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const searchTags = function() {
|
||||
return axios({
|
||||
method: 'PROPFIND',
|
||||
url: generateRemoteUrl('dav') + '/systemtags/',
|
||||
data: `<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<d:prop>
|
||||
<oc:id />
|
||||
<oc:display-name />
|
||||
<oc:user-visible />
|
||||
<oc:user-assignable />
|
||||
<oc:can-assign />
|
||||
</d:prop>
|
||||
</d:propfind>`
|
||||
}).then((response) => {
|
||||
return xmlToTagList(response.data)
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
searchTags
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
import MultiselectTag from './MultiselectTag'
|
||||
|
||||
export default MultiselectTag
|
||||
export { MultiselectTag }
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<div class="timeslot">
|
||||
<Multiselect v-model="newValue.timezone" :options="timezones" @input="update" />
|
||||
<input v-model="newValue.startTime" type="text" class="timeslot--start"
|
||||
placeholder="08:00" @input="update">
|
||||
<input v-model="newValue.endTime" type="text" placeholder="18:00"
|
||||
@input="update">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
|
||||
import moment from 'moment-timezone'
|
||||
import valueMixin from '../../mixins/valueMixin'
|
||||
|
||||
const zones = moment.tz.names()
|
||||
export default {
|
||||
name: 'RequestTime',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
mixins: [
|
||||
valueMixin
|
||||
],
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '1 MB'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timezones: zones,
|
||||
valid: false,
|
||||
newValue: {
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
timezone: moment.tz.guess()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateInternalValue(value) {
|
||||
var data = JSON.parse(value)
|
||||
var startTime = data[0].split(' ', 2)[0]
|
||||
var endTime = data[1].split(' ', 2)[0]
|
||||
var timezone = data[0].split(' ', 2)[1]
|
||||
this.newValue = {
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
timezone: timezone
|
||||
}
|
||||
},
|
||||
validate() {
|
||||
return this.newValue.startTime && this.newValue.startTime.match(/^(0[0-9]|1[0-9]|2[0-3]|[0-9]):[0-5][0-9]$/i) !== null
|
||||
&& this.newValue.endTime && this.newValue.endTime.match(/^(0[0-9]|1[0-9]|2[0-3]|[0-9]):[0-5][0-9]$/i) !== null
|
||||
&& moment.tz.zone(this.newValue.timezone) !== null
|
||||
},
|
||||
update() {
|
||||
if (this.validate()) {
|
||||
const output = `["${this.newValue.startTime} ${this.newValue.timezone}","${this.newValue.endTime} ${this.newValue.timezone}"]`
|
||||
this.$emit('input', output)
|
||||
this.valid = true
|
||||
} else {
|
||||
this.valid = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.timeslot {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-wrap: wrap;
|
||||
max-width: 180px;
|
||||
|
||||
.multiselect {
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 50%;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
&.timeslot--start {
|
||||
margin-right: 5px;
|
||||
width: calc(50% - 5px);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,137 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Multiselect
|
||||
:value="currentValue"
|
||||
:placeholder="t('workflowengine', 'Select a request URL')"
|
||||
label="label"
|
||||
track-by="pattern"
|
||||
group-values="children"
|
||||
group-label="label"
|
||||
:options="options" :multiple="false" :tagging="false"
|
||||
@input="setValue">
|
||||
<template slot="singleLabel" slot-scope="props">
|
||||
<span class="option__icon" :class="props.option.icon" />
|
||||
<span class="option__title option__title_single">{{ props.option.label }}</span>
|
||||
</template>
|
||||
<template slot="option" slot-scope="props">
|
||||
<span class="option__icon" :class="props.option.icon" />
|
||||
<span class="option__title">{{ props.option.label }} {{ props.option.$groupLabel }}</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
<input v-if="!isPredefined" type="text"
|
||||
:value="currentValue.pattern"
|
||||
:placeholder="placeholder" @input="updateCustom">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
|
||||
import valueMixin from '../../mixins/valueMixin'
|
||||
|
||||
export default {
|
||||
name: 'RequestURL',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
mixins: [
|
||||
valueMixin
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
newValue: '',
|
||||
predefinedTypes: [
|
||||
{
|
||||
label: t('workflowengine', 'Predefined URLs'),
|
||||
children: [
|
||||
{ pattern: 'webdav', label: t('workflowengine', 'Files WebDAV') }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
return [...this.predefinedTypes, this.customValue]
|
||||
},
|
||||
placeholder() {
|
||||
if (this.check.operator === 'matches' || this.check.operator === '!matches') {
|
||||
return '/^https\\:\\/\\/localhost\\/index\\.php$/i'
|
||||
}
|
||||
return 'https://localhost/index.php'
|
||||
},
|
||||
matchingPredefined() {
|
||||
return this.predefinedTypes
|
||||
.map(groups => groups.children)
|
||||
.flat()
|
||||
.find((type) => this.newValue === type.pattern)
|
||||
},
|
||||
isPredefined() {
|
||||
return !!this.matchingPredefined
|
||||
},
|
||||
customValue() {
|
||||
return {
|
||||
label: t('workflowengine', 'Others'),
|
||||
children: [
|
||||
{
|
||||
icon: 'icon-settings-dark',
|
||||
label: t('workflowengine', 'Custom URL'),
|
||||
pattern: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
currentValue() {
|
||||
if (this.matchingPredefined) {
|
||||
return this.matchingPredefined
|
||||
}
|
||||
return {
|
||||
icon: 'icon-settings-dark',
|
||||
label: t('workflowengine', 'Custom URL'),
|
||||
pattern: this.newValue
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateRegex(string) {
|
||||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/
|
||||
var result = regexRegex.exec(string)
|
||||
return result !== null
|
||||
},
|
||||
setValue(value) {
|
||||
// TODO: check if value requires a regex and set the check operator according to that
|
||||
if (value !== null) {
|
||||
this.newValue = value.pattern
|
||||
this.$emit('input', this.newValue)
|
||||
}
|
||||
},
|
||||
updateCustom(event) {
|
||||
this.newValue = event.target.value
|
||||
this.$emit('input', this.newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped src="./../../css/multiselect.css"></style>
|
|
@ -0,0 +1,133 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Multiselect
|
||||
:value="currentValue"
|
||||
:placeholder="t('workflowengine', 'Select a user agent')"
|
||||
label="label"
|
||||
track-by="pattern"
|
||||
group-values="children"
|
||||
group-label="label"
|
||||
:options="options" :multiple="false" :tagging="false"
|
||||
@input="setValue">
|
||||
<template slot="singleLabel" slot-scope="props">
|
||||
<span class="option__icon" :class="props.option.icon" />
|
||||
<span class="option__title option__title_single">{{ props.option.label }}</span>
|
||||
</template>
|
||||
<template slot="option" slot-scope="props">
|
||||
<span class="option__icon" :class="props.option.icon" />
|
||||
<span class="option__title">{{ props.option.label }} {{ props.option.$groupLabel }}</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
<input v-if="!isPredefined" type="text" :value="currentValue.pattern"
|
||||
@input="updateCustom">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
|
||||
import valueMixin from '../../mixins/valueMixin'
|
||||
|
||||
export default {
|
||||
name: 'RequestUserAgent',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
mixins: [
|
||||
valueMixin
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
newValue: '',
|
||||
predefinedTypes: [
|
||||
{
|
||||
label: t('workflowengine', 'Sync clients'),
|
||||
children: [
|
||||
{ pattern: 'android', label: t('workflowengine', 'Android client'), icon: 'icon-phone' },
|
||||
{ pattern: 'ios', label: t('workflowengine', 'iOS client'), icon: 'icon-phone' },
|
||||
{ pattern: 'desktop', label: t('workflowengine', 'Desktop client'), icon: 'icon-desktop' },
|
||||
{ pattern: 'mail', label: t('workflowengine', 'Thunderbird & Outlook addons'), icon: 'icon-mail' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
return [...this.predefinedTypes, this.customValue]
|
||||
},
|
||||
matchingPredefined() {
|
||||
return this.predefinedTypes
|
||||
.map(groups => groups.children)
|
||||
.flat()
|
||||
.find((type) => this.newValue === type.pattern)
|
||||
},
|
||||
isPredefined() {
|
||||
return !!this.matchingPredefined
|
||||
},
|
||||
customValue() {
|
||||
return {
|
||||
label: t('workflowengine', 'Others'),
|
||||
children: [
|
||||
{
|
||||
icon: 'icon-settings-dark',
|
||||
label: t('workflowengine', 'Custom user agent'),
|
||||
pattern: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
currentValue() {
|
||||
if (this.matchingPredefined) {
|
||||
return this.matchingPredefined
|
||||
}
|
||||
return {
|
||||
icon: 'icon-settings-dark',
|
||||
label: t('workflowengine', 'Custom user agent'),
|
||||
pattern: this.newValue
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateRegex(string) {
|
||||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/
|
||||
var result = regexRegex.exec(string)
|
||||
return result !== null
|
||||
},
|
||||
setValue(value) {
|
||||
// TODO: check if value requires a regex and set the check operator according to that
|
||||
if (value !== null) {
|
||||
this.newValue = value.pattern
|
||||
this.$emit('input', this.newValue)
|
||||
}
|
||||
},
|
||||
updateCustom(event) {
|
||||
this.newValue = event.target.value
|
||||
this.$emit('input', this.newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped src="./../../css/multiselect.css"></style>
|
|
@ -0,0 +1,77 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Multiselect v-model="newValue"
|
||||
:class="{'icon-loading-small': groups.length === 0}" :options="groups"
|
||||
:multiple="false"
|
||||
label="displayname" track-by="id"
|
||||
@input="setValue" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
|
||||
import valueMixin from '../../mixins/valueMixin'
|
||||
import axios from 'nextcloud-axios'
|
||||
export default {
|
||||
name: 'RequestUserGroup',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
mixins: [
|
||||
valueMixin
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
groups: []
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
axios.get(OC.linkToOCS('cloud', 2) + 'groups').then((response) => {
|
||||
this.groups = response.data.ocs.data.groups.reduce((obj, item) => {
|
||||
obj.push({
|
||||
id: item,
|
||||
displayname: item
|
||||
})
|
||||
return obj
|
||||
}, [])
|
||||
this.updateInternalValue(this.value)
|
||||
}, (error) => {
|
||||
console.error('Error while loading group list', error.response)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
updateInternalValue() {
|
||||
this.newValue = this.groups.find(group => group.id === this.value) || null
|
||||
},
|
||||
setValue(value) {
|
||||
if (value !== null) {
|
||||
this.$emit('input', this.newValue.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped src="./../../css/multiselect.css"></style>
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import { stringValidator, validateIPv4, validateIPv6 } from './../../helpers/validators'
|
||||
import FileMimeType from './FileMimeType'
|
||||
import FileSystemTag from './FileSystemTag'
|
||||
|
||||
const FileChecks = [
|
||||
{
|
||||
class: 'OCA\\WorkflowEngine\\Check\\FileName',
|
||||
name: t('workflowengine', 'File name'),
|
||||
operators: [
|
||||
{ operator: 'is', name: t('workflowengine', 'is') },
|
||||
{ operator: '!is', name: t('workflowengine', 'is not') },
|
||||
{ operator: 'matches', name: t('workflowengine', 'matches') },
|
||||
{ operator: '!matches', name: t('workflowengine', 'does not match') }
|
||||
],
|
||||
placeholder: (check) => {
|
||||
if (check.operator === 'matches' || check.operator === '!matches') {
|
||||
return '/^dummy-.+$/i'
|
||||
}
|
||||
return 'filename.txt'
|
||||
},
|
||||
validate: stringValidator
|
||||
},
|
||||
|
||||
{
|
||||
class: 'OCA\\WorkflowEngine\\Check\\FileMimeType',
|
||||
name: t('workflowengine', 'File MIME type'),
|
||||
operators: [
|
||||
{ operator: 'is', name: t('workflowengine', 'is') },
|
||||
{ operator: '!is', name: t('workflowengine', 'is not') },
|
||||
{ operator: 'matches', name: t('workflowengine', 'matches') },
|
||||
{ operator: '!matches', name: t('workflowengine', 'does not match') }
|
||||
],
|
||||
component: FileMimeType
|
||||
},
|
||||
|
||||
{
|
||||
class: 'OCA\\WorkflowEngine\\Check\\FileSize',
|
||||
name: t('workflowengine', 'File size (upload)'),
|
||||
operators: [
|
||||
{ operator: 'less', name: t('workflowengine', 'less') },
|
||||
{ operator: '!greater', name: t('workflowengine', 'less or equals') },
|
||||
{ operator: '!less', name: t('workflowengine', 'greater or equals') },
|
||||
{ operator: 'greater', name: t('workflowengine', 'greater') }
|
||||
],
|
||||
placeholder: (check) => '5 MB',
|
||||
validate: (check) => check.value.match(/^[0-9]+[ ]?[kmgt]?b$/i) !== null
|
||||
},
|
||||
|
||||
{
|
||||
class: 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress',
|
||||
name: t('workflowengine', 'Request remote address'),
|
||||
operators: [
|
||||
{ operator: 'matchesIPv4', name: t('workflowengine', 'matches IPv4') },
|
||||
{ operator: '!matchesIPv4', name: t('workflowengine', 'does not match IPv4') },
|
||||
{ operator: 'matchesIPv6', name: t('workflowengine', 'matches IPv6') },
|
||||
{ operator: '!matchesIPv6', name: t('workflowengine', 'does not match IPv6') }
|
||||
],
|
||||
placeholder: (check) => {
|
||||
if (check.operator === 'matchesIPv6' || check.operator === '!matchesIPv6') {
|
||||
return '::1/128'
|
||||
}
|
||||
return '127.0.0.1/32'
|
||||
},
|
||||
validate: (check) => {
|
||||
if (check.operator === 'matchesIPv6' || check.operator === '!matchesIPv6') {
|
||||
return validateIPv6(check.value)
|
||||
}
|
||||
return validateIPv4(check.value)
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
class: 'OCA\\WorkflowEngine\\Check\\FileSystemTags',
|
||||
name: t('workflowengine', 'File system tag'),
|
||||
operators: [
|
||||
{ operator: 'is', name: t('workflowengine', 'is tagged with') },
|
||||
{ operator: '!is', name: t('workflowengine', 'is not tagged with') }
|
||||
],
|
||||
component: FileSystemTag
|
||||
}
|
||||
]
|
||||
|
||||
export default FileChecks
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import FileChecks from './file'
|
||||
import RequestChecks from './request'
|
||||
|
||||
export default [...FileChecks, ...RequestChecks]
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import RequestUserAgent from './RequestUserAgent'
|
||||
import RequestTime from './RequestTime'
|
||||
import RequestURL from './RequestURL'
|
||||
import RequestUserGroup from './RequestUserGroup'
|
||||
|
||||
const RequestChecks = [
|
||||
{
|
||||
class: 'OCA\\WorkflowEngine\\Check\\RequestURL',
|
||||
name: t('workflowengine', 'Request URL'),
|
||||
operators: [
|
||||
{ operator: 'is', name: t('workflowengine', 'is') },
|
||||
{ operator: '!is', name: t('workflowengine', 'is not') },
|
||||
{ operator: 'matches', name: t('workflowengine', 'matches') },
|
||||
{ operator: '!matches', name: t('workflowengine', 'does not match') }
|
||||
],
|
||||
component: RequestURL
|
||||
},
|
||||
{
|
||||
class: 'OCA\\WorkflowEngine\\Check\\RequestTime',
|
||||
name: t('workflowengine', 'Request time'),
|
||||
operators: [
|
||||
{ operator: 'in', name: t('workflowengine', 'between') },
|
||||
{ operator: '!in', name: t('workflowengine', 'not between') }
|
||||
],
|
||||
component: RequestTime
|
||||
},
|
||||
{
|
||||
class: 'OCA\\WorkflowEngine\\Check\\RequestUserAgent',
|
||||
name: t('workflowengine', 'Request user agent'),
|
||||
operators: [
|
||||
{ operator: 'is', name: t('workflowengine', 'is') },
|
||||
{ operator: '!is', name: t('workflowengine', 'is not') },
|
||||
{ operator: 'matches', name: t('workflowengine', 'matches') },
|
||||
{ operator: '!matches', name: t('workflowengine', 'does not match') }
|
||||
],
|
||||
component: RequestUserAgent
|
||||
},
|
||||
{
|
||||
class: 'OCA\\WorkflowEngine\\Check\\UserGroupMembership',
|
||||
name: t('workflowengine', 'User group membership'),
|
||||
operators: [
|
||||
{ operator: 'is', name: t('workflowengine', 'is member of') },
|
||||
{ operator: '!is', name: t('workflowengine', 'is not member of') }
|
||||
],
|
||||
component: RequestUserGroup
|
||||
}
|
||||
]
|
||||
|
||||
export default RequestChecks
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="operation.isComplex && operation.fixedEntity !== ''" class="isComplex">
|
||||
<img class="option__icon" :src="entity.icon">
|
||||
<span class="option__title option__title_single">{{ operation.triggerHint }}</span>
|
||||
</div>
|
||||
<Multiselect v-else :value="currentEvent" :options="allEvents"
|
||||
label="eventName" track-by="id" :allow-empty="false"
|
||||
:disabled="allEvents.length <= 1" @input="updateEvent">
|
||||
<template slot="singleLabel" slot-scope="props">
|
||||
<img class="option__icon" :src="props.option.entity.icon">
|
||||
<span class="option__title option__title_single">{{ props.option.displayName }}</span>
|
||||
</template>
|
||||
<template slot="option" slot-scope="props">
|
||||
<img class="option__icon" :src="props.option.entity.icon">
|
||||
<span class="option__title">{{ props.option.displayName }}</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
|
||||
|
||||
export default {
|
||||
name: 'Event',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
props: {
|
||||
rule: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
entity() {
|
||||
return this.$store.getters.getEntityForOperation(this.operation)
|
||||
},
|
||||
operation() {
|
||||
return this.$store.getters.getOperationForRule(this.rule)
|
||||
},
|
||||
allEvents() {
|
||||
return this.$store.getters.getEventsForOperation(this.operation)
|
||||
},
|
||||
currentEvent() {
|
||||
if (!this.rule.events) {
|
||||
return this.allEvents.length > 0 ? this.allEvents[0] : null
|
||||
}
|
||||
return this.allEvents.find(event => event.entity.id === this.rule.entity && this.rule.events.indexOf(event.eventName) !== -1)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateEvent(event) {
|
||||
this.$set(this.rule, 'entity', event.entity.id)
|
||||
this.$set(this.rule, 'events', [event.eventName])
|
||||
this.$store.dispatch('updateRule', this.rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.isComplex {
|
||||
img {
|
||||
vertical-align: top;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
span {
|
||||
padding-top: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.multiselect::v-deep .multiselect__single {
|
||||
display: flex;
|
||||
}
|
||||
.multiselect:not(.multiselect--active)::v-deep .multiselect__tags {
|
||||
background-color: var(--color-main-background) !important;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.multiselect::v-deep .multiselect__tags .multiselect__single {
|
||||
background-color: var(--color-main-background) !important;
|
||||
}
|
||||
|
||||
.multiselect:not(.multiselect--disabled)::v-deep .multiselect__tags .multiselect__single {
|
||||
background-image: var(--icon-triangle-s-000);
|
||||
background-repeat: no-repeat;
|
||||
background-position: right center;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.option__title {
|
||||
margin-left: 5px;
|
||||
color: var(--color-main-text);
|
||||
}
|
||||
.option__title_single {
|
||||
font-weight: 900;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div class="actions__item" :class="{'colored': colored}" :style="{ backgroundColor: colored ? operation.color : 'transparent' }">
|
||||
<div class="icon" :class="operation.iconClass" :style="{ backgroundImage: operation.iconClass ? '' : `url(${operation.icon})` }" />
|
||||
<div class="actions__item__description">
|
||||
<h3>{{ operation.name }}</h3>
|
||||
<small>{{ operation.description }}</small>
|
||||
</div>
|
||||
<div class="actions__item_options">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Operation',
|
||||
props: {
|
||||
operation: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
colored: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.actions__item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
margin-left: -1px;
|
||||
padding: 10px;
|
||||
border-radius: var(--border-radius-large);
|
||||
margin-right: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-size: 50px 50px;
|
||||
background-position: center center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.actions__item__description {
|
||||
text-align: center;
|
||||
}
|
||||
.actions__item_options {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
h3, small {
|
||||
padding: 6px;
|
||||
display: block;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
small {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.colored {
|
||||
background-color: var(--color-primary-element);
|
||||
* {
|
||||
color: var(--color-primary-text)
|
||||
}
|
||||
}
|
||||
|
||||
.actions__item:not(.colored) {
|
||||
flex-direction: row;
|
||||
|
||||
.actions__item__description {
|
||||
padding-top: 5px;
|
||||
text-align: left;
|
||||
small {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
width: 50px;
|
||||
margin: 0;
|
||||
margin-right: 10px;
|
||||
&:not(.icon-invert) {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: those should be provided by the backend, remove once ready */
|
||||
.icon-block {
|
||||
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' height='32' width='32' version='1.1' viewBox='0 0 32 32'%3E%3Cpath fill='%23fff' d='m10.203 2-8.203 8.203v11.594l8.203 8.203h11.594l8.203-8.203v-11.594l-8.203-8.203h-11.594zm11.097 5.3092 3.345 3.3448-5.346 5.346 5.346 5.346-3.299 3.299-5.346-5.346-5.346 5.346-3.2992-3.299 5.3462-5.346-5.3462-5.346 3.2992-3.2992 5.346 5.3462 5.3-5.3918z'/%3E%3C/svg%3E");
|
||||
}
|
||||
.icon-convert-pdf {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' version='1.1' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23fff'%3E%3Cpath d='m7.0624 2.9056c-0.20526 0-0.36653 0.14989-0.36653 0.34066v8.8571c0 0.19077 0.16127 0.34066 0.36653 0.34066h8.0637c0.20526 0 0.36653-0.14989 0.36653-0.34066v-7.1538l-2.1992-2.044zm4.2518 2.6571s0.05132 0.64725-0.10996 1.567c0.52414 1.3987 1.0996 1.5875 1.3562 1.7033 0.54247-0.040873 1.1472-0.068129 1.6861 0.2044 0.36653 0.19486 0.65536 1.022-0.21992 1.022-0.39586-0.023161-1.1267-0.23574-1.6494-0.47692-0.78145 0.081762-1.752 0.21802-2.5657 0.54505-0.91633 1.4308-1.3268 1.6352-1.6494 1.6352-0.89067-0.21802-0.41052-1.3149-0.073304-1.4989 0.40319-0.32022 0.87601-0.50417 1.0263-0.54505 0.065969-0.10221 1.0146-1.8327 1.2462-2.5549-0.21992-0.69767-0.27123-1.4349-0.14661-1.8736 0.57179-0.69358 1.0996-0.23846 1.0996 0.27253zm-0.51315 2.1121c-0.19793 0.72015-0.98817 2.1012-0.95299 2.044 0.81004-0.33044 1.5394-0.42923 2.3458-0.54505-0.38559-0.16011-0.84009-0.17033-1.3928-1.4989z' stroke-width='.70672'/%3E%3Cpath d='m16.246-9.7651c-2.05e-4 0.0144-6e-3 0.027629-6e-3 0.042066-0.0044 2.2592 2.0761 3.742 4.0564 3.6477v1.2349l2.3737-2.2265-2.3377-2.3407-3e-3 1.2289c-1.0287 0.1337-1.8811-0.66867-1.8659-1.5414 2.9e-4 -0.016152 0.0083-0.029062 9e-3 -0.045071z' stroke-width='.67694'/%3E%3Cpath d='m3.2734 5.1094v1.4492h-2.7676v2.5h2.7246l-0.0019532 1.4629 3.0996-2.6387-3.0547-2.7734z'/%3E%3Cpath d='m8.334-11.356c-0.78035-0.78051-1.9205-1.0863-2.9866-0.80073a0.51533 0.51533 0 1 0 0.26293 0.99405c0.71208-0.19075 1.4702 0.01747 1.9914 0.53876 0.46076 0.46083 0.65567 1.1026 0.56688 1.7376a0.61838 0.61838 0 1 0-0.87225 0.87442l0.8687 0.86886a0.61838 0.61838 0 0 0 0.86992 7.91e-5l0.86886-0.8687a0.61838 0.61838 0 0 0 0.0011543-0.88702 0.61838 0.61838 0 0 0-0.67634-0.12303c0.04094-0.86013-0.27221-1.7117-0.89472-2.3343zm-3.3067 1.0814a0.61838 0.61838 0 0 0-0.015967-0.01364l-0.86984-0.87a0.61838 0.61838 0 0 0-0.042126-0.04213 0.61838 0.61838 0 0 0-0.82551 0.04205l-0.87 0.86984a0.61838 0.61838 0 0 0 0.66145 1.0237c-0.024276 0.84049 0.29182 1.6675 0.90045 2.2762 0.78035 0.78052 1.9205 1.0863 2.9866 0.80073a0.51533 0.51533 0 1 0-0.27202-0.99408c-0.71208 0.19075-1.4669-0.011716-1.988-0.53306-0.45484-0.45491-0.65183-1.0905-0.57258-1.7183l0.018216 0.018221a0.61843 0.61843 0 0 0 0.88935-0.85959z' stroke-width='.68342'/%3E%3Cpath d='m31.219 0.33675v0.00113h-6.9286v1.3295l6.9286 0.036145c0.0026-1.821e-4 0.0053 2.074e-4 0.0079 0 0.0053-4.166e-4 0.01058-0.00137 0.01581-0.00113 0.65203-0.00106 1.1749 0.44619 1.1867 1.0392 0.0108 0.5673-0.60099 1.0888-1.3381 1.0019l-0.0013-0.79858-1.6753 1.5203 1.7016 1.4481-0.0013-0.8031c1.419 0.06127 2.9112-0.90236 2.9081-2.3709-0.0029-1.3197-1.2547-2.4007-2.7961-2.4014-0.0023-1e-6 -0.0043-0.00113-0.0066-0.00113z' stroke-width='.462'/%3E%3Crect x='31.116' y='-1.6777' width='4.3279' height='7.5909'/%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
.colored .icon-invert {
|
||||
filter: invert(1);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,249 @@
|
|||
<template>
|
||||
<div class="section rule" :style="{ borderLeftColor: operation.color || '' }">
|
||||
<div class="trigger">
|
||||
<p>
|
||||
<span>{{ t('workflowengine', 'When') }}</span>
|
||||
<Event :rule="rule" @update="updateRule" />
|
||||
</p>
|
||||
<p v-for="(check, index) in rule.checks" :key="index">
|
||||
<span>{{ t('workflowengine', 'and') }}</span>
|
||||
<Check :check="check" :rule="rule" @update="updateRule"
|
||||
@remove="removeCheck(check)" />
|
||||
</p>
|
||||
<p>
|
||||
<span />
|
||||
<input v-if="lastCheckComplete" type="button" class="check--add"
|
||||
value="Add a new filter" @click="rule.checks.push({class: null, operator: null, value: null})">
|
||||
</p>
|
||||
</div>
|
||||
<div class="flow-icon icon-confirm" />
|
||||
<div class="action">
|
||||
<div class="buttons">
|
||||
<Actions>
|
||||
<ActionButton v-if="rule.id < -1" icon="icon-close" @click="cancelRule">
|
||||
{{ t('workflowengine', 'Cancel rule creation') }}
|
||||
</ActionButton>
|
||||
<ActionButton v-else icon="icon-close" @click="deleteRule">
|
||||
{{ t('workflowengine', 'Remove rule') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</div>
|
||||
<Operation :operation="operation" :colored="false">
|
||||
<component :is="operation.options" v-if="operation.options" v-model="rule.operation"
|
||||
@input="updateOperation" />
|
||||
</Operation>
|
||||
<button v-tooltip="ruleStatus.tooltip" class="status-button icon" :class="ruleStatus.class"
|
||||
@click="saveRule">
|
||||
{{ ruleStatus.title }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Tooltip } from 'nextcloud-vue/dist/Directives/Tooltip'
|
||||
import { Actions } from 'nextcloud-vue/dist/Components/Actions'
|
||||
import { ActionButton } from 'nextcloud-vue/dist/Components/ActionButton'
|
||||
import Event from './Event'
|
||||
import Check from './Check'
|
||||
import Operation from './Operation'
|
||||
|
||||
export default {
|
||||
name: 'Rule',
|
||||
components: {
|
||||
Operation, Check, Event, Actions, ActionButton
|
||||
},
|
||||
directives: {
|
||||
Tooltip
|
||||
},
|
||||
props: {
|
||||
rule: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editing: false,
|
||||
checks: [],
|
||||
error: null,
|
||||
dirty: this.rule.id < 0,
|
||||
checking: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
operation() {
|
||||
return this.$store.getters.getOperationForRule(this.rule)
|
||||
},
|
||||
ruleStatus() {
|
||||
if (this.error || !this.rule.valid) {
|
||||
return {
|
||||
title: t('workflowengine', 'The configuration is invalid'),
|
||||
class: 'icon-close-white invalid',
|
||||
tooltip: { placement: 'bottom', show: true, content: this.error }
|
||||
}
|
||||
}
|
||||
if (!this.dirty || this.checking) {
|
||||
return { title: 'Active', class: 'icon icon-checkmark' }
|
||||
}
|
||||
return { title: 'Save', class: 'icon-confirm-white primary' }
|
||||
|
||||
},
|
||||
lastCheckComplete() {
|
||||
const lastCheck = this.rule.checks[this.rule.checks.length - 1]
|
||||
return typeof lastCheck === 'undefined' || lastCheck.class !== null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async updateOperation(operation) {
|
||||
this.$set(this.rule, 'operation', operation)
|
||||
await this.updateRule()
|
||||
},
|
||||
async updateRule() {
|
||||
this.checking = true
|
||||
if (!this.dirty) {
|
||||
this.dirty = true
|
||||
}
|
||||
try {
|
||||
// TODO: add new verify endpoint
|
||||
// let result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations/test`), this.rule)
|
||||
this.error = null
|
||||
this.checking = false
|
||||
this.$store.dispatch('updateRule', this.rule)
|
||||
} catch (e) {
|
||||
console.error('Failed to update operation', e)
|
||||
this.error = e.response.ocs.meta.message
|
||||
this.checking = false
|
||||
}
|
||||
},
|
||||
async saveRule() {
|
||||
try {
|
||||
await this.$store.dispatch('pushUpdateRule', this.rule)
|
||||
this.dirty = false
|
||||
this.error = null
|
||||
} catch (e) {
|
||||
console.error('Failed to save operation')
|
||||
this.error = e.response.data.ocs.meta.message
|
||||
}
|
||||
},
|
||||
async deleteRule() {
|
||||
try {
|
||||
await this.$store.dispatch('deleteRule', this.rule)
|
||||
} catch (e) {
|
||||
console.error('Failed to delete operation')
|
||||
this.error = e.response.data.ocs.meta.message
|
||||
}
|
||||
},
|
||||
cancelRule() {
|
||||
this.$store.dispatch('removeRule', this.rule)
|
||||
},
|
||||
async removeCheck(check) {
|
||||
const index = this.rule.checks.findIndex(item => item === check)
|
||||
if (index > -1) {
|
||||
this.$delete(this.rule.checks, index)
|
||||
}
|
||||
this.$store.dispatch('updateRule', this.rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
button.icon {
|
||||
padding-left: 32px;
|
||||
background-position: 10px center;
|
||||
}
|
||||
|
||||
.status-button {
|
||||
transition: 0.5s ease all;
|
||||
display: block;
|
||||
margin: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
.status-button.primary {
|
||||
padding-left: 32px;
|
||||
background-position: 10px center;
|
||||
}
|
||||
.status-button:not(.primary) {
|
||||
background-color: var(--color-main-background);
|
||||
}
|
||||
.status-button.invalid {
|
||||
background-color: var(--color-warning);
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.flow-icon {
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
.rule {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
border-left: 5px solid var(--color-primary-element);
|
||||
|
||||
.trigger, .action {
|
||||
flex-grow: 1;
|
||||
min-height: 100px;
|
||||
max-width: 700px;
|
||||
}
|
||||
.action {
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
.buttons {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
.icon-confirm {
|
||||
background-position: right 27px;
|
||||
padding-right: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
.trigger p, .action p {
|
||||
min-height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > span {
|
||||
min-width: 50px;
|
||||
text-align: right;
|
||||
color: var(--color-text-maxcontrast);
|
||||
padding-right: 10px;
|
||||
padding-top: 7px;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
.multiselect {
|
||||
flex-grow: 1;
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.check--add {
|
||||
background-position: 7px center;
|
||||
background-color: transparent;
|
||||
padding-left: 6px;
|
||||
margin: 0;
|
||||
width: 180px;
|
||||
border-radius: var(--border-radius);
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
@media (max-width:1400px) {
|
||||
.rule {
|
||||
&, .trigger, .action {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.flow-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,126 @@
|
|||
<template>
|
||||
<div id="workflowengine">
|
||||
<div class="section">
|
||||
<h2>{{ t('workflowengine', 'Workflows') }}</h2>
|
||||
|
||||
<transition-group name="slide" tag="div" class="actions">
|
||||
<Operation v-for="operation in getMainOperations" :key="operation.id" :operation="operation"
|
||||
@click.native="createNewRule(operation)" />
|
||||
</transition-group>
|
||||
|
||||
<div v-if="hasMoreOperations" class="actions__more">
|
||||
<button class="icon" :class="showMoreOperations ? 'icon-triangle-n' : 'icon-triangle-s'"
|
||||
@click="showMoreOperations=!showMoreOperations">
|
||||
{{ showMoreOperations ? t('workflowengine', 'Show less') : t('workflowengine', 'Show more') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition-group v-if="rules.length > 0" name="slide">
|
||||
<Rule v-for="rule in rules" :key="rule.id" :rule="rule" />
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Rule from './Rule'
|
||||
import Operation from './Operation'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
|
||||
const ACTION_LIMIT = 3
|
||||
|
||||
export default {
|
||||
name: 'Workflow',
|
||||
components: {
|
||||
Operation,
|
||||
Rule
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMoreOperations: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
rules: 'getRules'
|
||||
}),
|
||||
...mapState({
|
||||
operations: 'operations'
|
||||
}),
|
||||
hasMoreOperations() {
|
||||
return Object.keys(this.operations).length > ACTION_LIMIT
|
||||
},
|
||||
getMainOperations() {
|
||||
if (this.showMoreOperations) {
|
||||
return Object.values(this.operations)
|
||||
}
|
||||
return Object.values(this.operations).slice(0, ACTION_LIMIT)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('fetchRules')
|
||||
},
|
||||
methods: {
|
||||
createNewRule(operation) {
|
||||
this.$store.dispatch('createNewRule', operation)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
#workflowengine {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.section {
|
||||
max-width: 100vw;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
max-width: 900px;
|
||||
.actions__item {
|
||||
max-width: 280px;
|
||||
flex-basis: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
button.icon {
|
||||
padding-left: 32px;
|
||||
background-position: 10px center;
|
||||
}
|
||||
|
||||
.slide-enter-active {
|
||||
-moz-transition-duration: 0.3s;
|
||||
-webkit-transition-duration: 0.3s;
|
||||
-o-transition-duration: 0.3s;
|
||||
transition-duration: 0.3s;
|
||||
-moz-transition-timing-function: ease-in;
|
||||
-webkit-transition-timing-function: ease-in;
|
||||
-o-transition-timing-function: ease-in;
|
||||
transition-timing-function: ease-in;
|
||||
}
|
||||
|
||||
.slide-leave-active {
|
||||
-moz-transition-duration: 0.3s;
|
||||
-webkit-transition-duration: 0.3s;
|
||||
-o-transition-duration: 0.3s;
|
||||
transition-duration: 0.3s;
|
||||
-moz-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
-webkit-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
-o-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
.slide-enter-to, .slide-leave {
|
||||
max-height: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slide-enter, .slide-leave-to {
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
.multiselect::v-deep .multiselect__single {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.option__icon {
|
||||
min-width: 25px;
|
||||
}
|
||||
|
||||
input, .multiselect {
|
||||
width: 100%;
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @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.FileMimeTypePlugin = {
|
||||
getCheck: function() {
|
||||
return {
|
||||
'class': 'OCA\\WorkflowEngine\\Check\\FileMimeType',
|
||||
'name': t('workflowengine', 'File MIME type'),
|
||||
'operators': [
|
||||
{'operator': 'is', 'name': t('workflowengine', 'is')},
|
||||
{'operator': '!is', 'name': t('workflowengine', 'is not')},
|
||||
{'operator': 'matches', 'name': t('workflowengine', 'matches')},
|
||||
{'operator': '!matches', 'name': t('workflowengine', 'does not match')}
|
||||
]
|
||||
};
|
||||
},
|
||||
render: function(element, check) {
|
||||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileMimeType') {
|
||||
return;
|
||||
}
|
||||
|
||||
var placeholder = 'text/plain';
|
||||
if (check['operator'] === 'matches' || check['operator'] === '!matches') {
|
||||
placeholder = '/^text\\/(plain|html)$/i';
|
||||
|
||||
if (this._validateRegex(check['value'])) {
|
||||
$(element).removeClass('invalid-input');
|
||||
} else {
|
||||
$(element).addClass('invalid-input');
|
||||
}
|
||||
}
|
||||
|
||||
$(element).css('width', '250px')
|
||||
.attr('placeholder', placeholder)
|
||||
.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
|
||||
.addClass('has-tooltip')
|
||||
.tooltip({
|
||||
placement: 'bottom'
|
||||
});
|
||||
},
|
||||
|
||||
_validateRegex: function(string) {
|
||||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
|
||||
result = regexRegex.exec(string);
|
||||
return result !== null;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileMimeTypePlugin);
|
|
@ -1,78 +0,0 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2018 Daniel Kesselberg <mail@danielkesselberg.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.FileNamePlugin = {
|
||||
getCheck: function () {
|
||||
return {
|
||||
'class': 'OCA\\WorkflowEngine\\Check\\FileName',
|
||||
'name': t('workflowengine', 'File name'),
|
||||
'operators': [
|
||||
{'operator': 'is', 'name': t('workflowengine', 'is')},
|
||||
{'operator': '!is', 'name': t('workflowengine', 'is not')},
|
||||
{
|
||||
'operator': 'matches',
|
||||
'name': t('workflowengine', 'matches')
|
||||
},
|
||||
{
|
||||
'operator': '!matches',
|
||||
'name': t('workflowengine', 'does not match')
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
render: function (element, check) {
|
||||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileName') {
|
||||
return;
|
||||
}
|
||||
|
||||
var placeholder = 'dummy.jpg';
|
||||
if (check['operator'] === 'matches' || check['operator'] === '!matches') {
|
||||
placeholder = '/^dummy-.+$/i';
|
||||
|
||||
if (this._validateRegex(check['value'])) {
|
||||
$(element).removeClass('invalid-input');
|
||||
} else {
|
||||
$(element).addClass('invalid-input');
|
||||
}
|
||||
}
|
||||
|
||||
$(element).css('width', '250px')
|
||||
.attr('placeholder', placeholder)
|
||||
.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
|
||||
.addClass('has-tooltip')
|
||||
.tooltip({
|
||||
placement: 'bottom'
|
||||
});
|
||||
},
|
||||
|
||||
_validateRegex: function (string) {
|
||||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
|
||||
result = regexRegex.exec(string);
|
||||
return result !== null;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileNamePlugin);
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @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.FileSizePlugin = {
|
||||
getCheck: function() {
|
||||
return {
|
||||
'class': 'OCA\\WorkflowEngine\\Check\\FileSize',
|
||||
'name': t('workflowengine', 'File size (upload)'),
|
||||
'operators': [
|
||||
{'operator': 'less', 'name': t('workflowengine', 'less')},
|
||||
{'operator': '!greater', 'name': t('workflowengine', 'less or equals')},
|
||||
{'operator': '!less', 'name': t('workflowengine', 'greater or equals')},
|
||||
{'operator': 'greater', 'name': t('workflowengine', 'greater')}
|
||||
]
|
||||
};
|
||||
},
|
||||
render: function(element, check) {
|
||||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileSize') {
|
||||
return;
|
||||
}
|
||||
|
||||
var placeholder = '12 MB'; // Do not translate!!!
|
||||
$(element).css('width', '250px')
|
||||
.attr('placeholder', placeholder)
|
||||
.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
|
||||
.addClass('has-tooltip')
|
||||
.tooltip({
|
||||
placement: 'bottom'
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileSizePlugin);
|
|
@ -1,78 +0,0 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @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.FileSystemTagsPlugin = {
|
||||
getCheck: function() {
|
||||
this.collection = OC.SystemTags.collection;
|
||||
|
||||
return {
|
||||
'class': 'OCA\\WorkflowEngine\\Check\\FileSystemTags',
|
||||
'name': t('workflowengine', 'File system tag'),
|
||||
'operators': [
|
||||
{'operator': 'is', 'name': t('workflowengine', 'is tagged with')},
|
||||
{'operator': '!is', 'name': t('workflowengine', 'is not tagged with')}
|
||||
]
|
||||
};
|
||||
},
|
||||
render: function(element, check) {
|
||||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileSystemTags') {
|
||||
return;
|
||||
}
|
||||
|
||||
$(element).css('width', '400px');
|
||||
|
||||
$(element).select2({
|
||||
allowClear: false,
|
||||
multiple: false,
|
||||
placeholder: t('workflowengine', 'Select tag…'),
|
||||
query: _.debounce(function(query) {
|
||||
query.callback({
|
||||
results: OC.SystemTags.collection.filterByName(query.term)
|
||||
});
|
||||
}, 100, true),
|
||||
id: function(element) {
|
||||
return element.get('id');
|
||||
},
|
||||
initSelection: function(element, callback) {
|
||||
callback($(element).val());
|
||||
},
|
||||
formatResult: function (tag) {
|
||||
return OC.SystemTags.getDescriptiveTag(tag);
|
||||
},
|
||||
formatSelection: function (tagId) {
|
||||
var tag = OC.SystemTags.collection.get(tagId);
|
||||
if (!_.isUndefined(tag)) {
|
||||
return OC.SystemTags.getDescriptiveTag(tag);
|
||||
}
|
||||
},
|
||||
escapeMarkup: function(m) {
|
||||
return m;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileSystemTagsPlugin);
|
|
@ -1,7 +0,0 @@
|
|||
module.exports = function(classname) {
|
||||
var check = OCA.WorkflowEngine.getCheckByClass(classname);
|
||||
if (!_.isUndefined(check)) {
|
||||
return check['operators'];
|
||||
}
|
||||
return [];
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
module.exports = function(currentValue, itemValue) {
|
||||
if (currentValue === itemValue) {
|
||||
return 'selected="selected"';
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const getApiUrl = (url) => {
|
||||
const scopeValue = OCP.InitialState.loadState('workflowengine', 'scope') === 0 ? 'global' : 'user'
|
||||
return OC.linkToOCS('apps/workflowengine/api/v1/workflows', 2) + scopeValue + url + '?format=json'
|
||||
}
|
||||
|
||||
export {
|
||||
getApiUrl
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const validateRegex = function(string) {
|
||||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/
|
||||
var result = regexRegex.exec(string)
|
||||
return result !== null
|
||||
}
|
||||
|
||||
const validateIPv4 = function(string) {
|
||||
var regexRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[1-2][0-9]|[1-9])$/
|
||||
var result = regexRegex.exec(string)
|
||||
return result !== null
|
||||
}
|
||||
|
||||
const validateIPv6 = function(string) {
|
||||
var regexRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9])$/
|
||||
var result = regexRegex.exec(string)
|
||||
return result !== null
|
||||
}
|
||||
|
||||
const stringValidator = (check) => {
|
||||
if (check.operator === 'matches' || check.operator === '!matches') {
|
||||
return validateRegex(check.value)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export { validateRegex, stringValidator, validateIPv4, validateIPv6 }
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const valueMixin = {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
check: {
|
||||
type: Object,
|
||||
default: () => { return {} }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newValue: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: function(value) {
|
||||
this.updateInternalValue(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateInternalValue(value) {
|
||||
this.newValue = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default valueMixin
|
|
@ -1,83 +0,0 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @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.RequestRemoteAddressPlugin = {
|
||||
getCheck: function() {
|
||||
return {
|
||||
'class': 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress',
|
||||
'name': t('workflowengine', 'Request remote address'),
|
||||
'operators': [
|
||||
{'operator': 'matchesIPv4', 'name': t('workflowengine', 'matches IPv4')},
|
||||
{'operator': '!matchesIPv4', 'name': t('workflowengine', 'does not match IPv4')},
|
||||
{'operator': 'matchesIPv6', 'name': t('workflowengine', 'matches IPv6')},
|
||||
{'operator': '!matchesIPv6', 'name': t('workflowengine', 'does not match IPv6')}
|
||||
]
|
||||
};
|
||||
},
|
||||
render: function(element, check) {
|
||||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress') {
|
||||
return;
|
||||
}
|
||||
|
||||
var placeholder = '127.0.0.1/32'; // Do not translate!!!
|
||||
if (check['operator'] === 'matchesIPv6' || check['operator'] === '!matchesIPv6') {
|
||||
placeholder = '::1/128'; // Do not translate!!!
|
||||
if (this._validateIPv6(check['value'])) {
|
||||
$(element).removeClass('invalid-input');
|
||||
} else {
|
||||
$(element).addClass('invalid-input');
|
||||
}
|
||||
} else {
|
||||
if (this._validateIPv4(check['value'])) {
|
||||
$(element).removeClass('invalid-input');
|
||||
} else {
|
||||
$(element).addClass('invalid-input');
|
||||
}
|
||||
}
|
||||
|
||||
$(element).css('width', '300px')
|
||||
.attr('placeholder', placeholder)
|
||||
.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
|
||||
.addClass('has-tooltip')
|
||||
.tooltip({
|
||||
placement: 'bottom'
|
||||
});
|
||||
},
|
||||
|
||||
_validateIPv4: function(string) {
|
||||
var regexRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[1-2][0-9]|[1-9])$/,
|
||||
result = regexRegex.exec(string);
|
||||
return result !== null;
|
||||
},
|
||||
|
||||
_validateIPv6: function(string) {
|
||||
var regexRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9])$/,
|
||||
result = regexRegex.exec(string);
|
||||
return result !== null;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestRemoteAddressPlugin);
|
|
@ -1,196 +0,0 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @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.RequestTimePlugin = {
|
||||
timezones: [
|
||||
"Europe/Berlin",
|
||||
"Europe/London"
|
||||
],
|
||||
_$element: null,
|
||||
getCheck: function() {
|
||||
return {
|
||||
'class': 'OCA\\WorkflowEngine\\Check\\RequestTime',
|
||||
'name': t('workflowengine', 'Request time'),
|
||||
'operators': [
|
||||
{'operator': 'in', 'name': t('workflowengine', 'between')},
|
||||
{'operator': '!in', 'name': t('workflowengine', 'not between')}
|
||||
]
|
||||
};
|
||||
},
|
||||
render: function(element, check) {
|
||||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestTime') {
|
||||
return;
|
||||
}
|
||||
|
||||
var startTime = '09:00',
|
||||
endTime = '18:00',
|
||||
timezone = jstz.determine().name(),
|
||||
$element = $(element);
|
||||
|
||||
if (_.isString(check['value']) && check['value'] !== '') {
|
||||
var value = JSON.parse(check['value']),
|
||||
splittedStart = value[0].split(' ', 2),
|
||||
splittedEnd = value[1].split(' ', 2);
|
||||
|
||||
startTime = splittedStart[0];
|
||||
endTime = splittedEnd[0];
|
||||
timezone = splittedStart[1];
|
||||
}
|
||||
|
||||
var valueJSON = JSON.stringify([startTime + ' ' + timezone, endTime + ' ' + timezone]);
|
||||
if (check['value'] !== valueJSON) {
|
||||
check['value'] = valueJSON;
|
||||
$element.val(valueJSON);
|
||||
}
|
||||
|
||||
$element.css('display', 'none');
|
||||
|
||||
$('<input>')
|
||||
.attr('type', 'text')
|
||||
.attr('placeholder', t('workflowengine', 'Start'))
|
||||
.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: '16:00'}))
|
||||
.addClass('has-tooltip')
|
||||
.tooltip({
|
||||
placement: 'bottom'
|
||||
})
|
||||
.addClass('start')
|
||||
.val(startTime)
|
||||
.insertBefore($element);
|
||||
$('<input>')
|
||||
.attr('type', 'text')
|
||||
.attr('placeholder', t('workflowengine', 'End'))
|
||||
.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: '16:00'}))
|
||||
.addClass('has-tooltip')
|
||||
.tooltip({
|
||||
placement: 'bottom'
|
||||
})
|
||||
.addClass('end')
|
||||
.val(endTime)
|
||||
.insertBefore($element);
|
||||
|
||||
var timezoneInput = $('<input>')
|
||||
.attr('type', 'hidden')
|
||||
.css('width', '250px')
|
||||
.insertBefore($element)
|
||||
.val(timezone);
|
||||
|
||||
timezoneInput.select2({
|
||||
allowClear: false,
|
||||
multiple: false,
|
||||
placeholder: t('workflowengine', 'Select timezone…'),
|
||||
ajax: {
|
||||
url: OC.generateUrl('apps/workflowengine/timezones'),
|
||||
dataType: 'json',
|
||||
quietMillis: 100,
|
||||
data: function (term) {
|
||||
if (term === '') {
|
||||
// Default search in the same continent...
|
||||
term = jstz.determine().name().split('/');
|
||||
term = term[0];
|
||||
}
|
||||
return {
|
||||
search: term
|
||||
};
|
||||
},
|
||||
results: function (response) {
|
||||
var results = [];
|
||||
$.each(response, function(timezone) {
|
||||
results.push({ id: timezone });
|
||||
});
|
||||
|
||||
return {
|
||||
results: results,
|
||||
more: false
|
||||
};
|
||||
}
|
||||
},
|
||||
initSelection: function (element, callback) {
|
||||
callback(element.val());
|
||||
},
|
||||
formatResult: function (element) {
|
||||
return '<span>' + element.id + '</span>';
|
||||
},
|
||||
formatSelection: function (element) {
|
||||
if (!_.isUndefined(element.id)) {
|
||||
element = element.id;
|
||||
}
|
||||
return '<span>' + element + '</span>';
|
||||
}
|
||||
});
|
||||
|
||||
// Has to be added after select2 for `event.target.classList`
|
||||
timezoneInput.addClass('timezone');
|
||||
|
||||
$element.parent()
|
||||
.on('change', '.start', _.bind(this.update, this))
|
||||
.on('change', '.end', _.bind(this.update, this))
|
||||
.on('change', '.timezone', _.bind(this.update, this));
|
||||
|
||||
this._$element = $element;
|
||||
},
|
||||
update: function(event) {
|
||||
var value = event.target.value,
|
||||
key = null;
|
||||
|
||||
for (var i = 0; i < event.target.classList.length; i++) {
|
||||
key = event.target.classList[i];
|
||||
}
|
||||
|
||||
if (key === null) {
|
||||
console.warn('update triggered but element doesn\'t have any class');
|
||||
return;
|
||||
}
|
||||
|
||||
var data = JSON.parse(this._$element.val()),
|
||||
startTime = moment(data[0].split(' ', 2)[0], 'H:m Z'),
|
||||
endTime = moment(data[1].split(' ', 2)[0], 'H:m Z'),
|
||||
timezone = data[0].split(' ', 2)[1];
|
||||
|
||||
if (key === 'start' || key === 'end') {
|
||||
var parsedDate = moment(value, ['H:m', 'h:m a'], true).format('HH:mm');
|
||||
|
||||
if (parsedDate === 'Invalid date') {
|
||||
return;
|
||||
}
|
||||
|
||||
var indexValue = 0;
|
||||
if (key === 'end') {
|
||||
indexValue = 1;
|
||||
}
|
||||
data[indexValue] = parsedDate + ' ' + timezone;
|
||||
}
|
||||
|
||||
if (key === 'timezone') {
|
||||
data[0] = startTime.format('HH:mm') + ' ' + value;
|
||||
data[1] = endTime.format('HH:mm') + ' ' + value;
|
||||
}
|
||||
|
||||
this._$element.val(JSON.stringify(data));
|
||||
this._$element.trigger('change');
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestTimePlugin);
|
|
@ -1,117 +0,0 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @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.RequestURLPlugin = {
|
||||
predefinedValues: ['webdav'],
|
||||
getCheck: function() {
|
||||
return {
|
||||
'class': 'OCA\\WorkflowEngine\\Check\\RequestURL',
|
||||
'name': t('workflowengine', 'Request URL'),
|
||||
'operators': [
|
||||
{'operator': 'is', 'name': t('workflowengine', 'is')},
|
||||
{'operator': '!is', 'name': t('workflowengine', 'is not')},
|
||||
{'operator': 'matches', 'name': t('workflowengine', 'matches')},
|
||||
{'operator': '!matches', 'name': t('workflowengine', 'does not match')}
|
||||
]
|
||||
};
|
||||
},
|
||||
render: function(element, check) {
|
||||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestURL') {
|
||||
return;
|
||||
}
|
||||
|
||||
var placeholder = 'https://localhost/index.php';
|
||||
|
||||
if (check['operator'] === 'matches' || check['operator'] === '!matches') {
|
||||
placeholder = '/^https\\:\\/\\/localhost\\/index\\.php$/i';
|
||||
}
|
||||
|
||||
$(element).css('width', '250px')
|
||||
.attr('placeholder', placeholder)
|
||||
.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
|
||||
.addClass('has-tooltip')
|
||||
.tooltip({
|
||||
placement: 'bottom'
|
||||
});
|
||||
|
||||
if (check['operator'] === 'matches' || check['operator'] === '!matches') {
|
||||
if (this._validateRegex(check['value'])) {
|
||||
$(element).removeClass('invalid-input');
|
||||
} else {
|
||||
$(element).addClass('invalid-input');
|
||||
}
|
||||
} else {
|
||||
var self = this,
|
||||
data = [
|
||||
{
|
||||
text: t('workflowengine', 'Predefined URLs'),
|
||||
children: [
|
||||
{id: 'webdav', text: t('workflowengine', 'Files WebDAV')}
|
||||
]
|
||||
}
|
||||
];
|
||||
if (this.predefinedValues.indexOf(check['value']) === -1) {
|
||||
data.unshift({
|
||||
id: check['value'],
|
||||
text: check['value']
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
$(element).select2({
|
||||
data: data,
|
||||
createSearchChoice: function(term) {
|
||||
if (self.predefinedValues.indexOf(check['value']) === -1) {
|
||||
return {
|
||||
id: term,
|
||||
text: term
|
||||
};
|
||||
}
|
||||
},
|
||||
id: function(element) {
|
||||
return element.id;
|
||||
},
|
||||
formatResult: function (tag) {
|
||||
return tag.text;
|
||||
},
|
||||
formatSelection: function (tag) {
|
||||
return tag.text;
|
||||
},
|
||||
escapeMarkup: function(m) {
|
||||
return m;
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
_validateRegex: function(string) {
|
||||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
|
||||
result = regexRegex.exec(string);
|
||||
return result !== null;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestURLPlugin);
|
|
@ -1,119 +0,0 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @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.RequestUserAgentPlugin = {
|
||||
predefinedValues: ['android', 'ios', 'desktop'],
|
||||
getCheck: function() {
|
||||
return {
|
||||
'class': 'OCA\\WorkflowEngine\\Check\\RequestUserAgent',
|
||||
'name': t('workflowengine', 'Request user agent'),
|
||||
'operators': [
|
||||
{'operator': 'is', 'name': t('workflowengine', 'is')},
|
||||
{'operator': '!is', 'name': t('workflowengine', 'is not')},
|
||||
{'operator': 'matches', 'name': t('workflowengine', 'matches')},
|
||||
{'operator': '!matches', 'name': t('workflowengine', 'does not match')}
|
||||
]
|
||||
};
|
||||
},
|
||||
render: function(element, check) {
|
||||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestUserAgent') {
|
||||
return;
|
||||
}
|
||||
|
||||
var placeholder = 'Mozilla/5.0 User Agent';
|
||||
|
||||
if (check.operator === 'matches' || check.operator === '!matches') {
|
||||
placeholder = '/^Mozilla\\/5\\.0 (.*)$/i';
|
||||
}
|
||||
|
||||
$(element).css('width', '250px')
|
||||
.attr('placeholder', placeholder)
|
||||
.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
|
||||
.addClass('has-tooltip')
|
||||
.tooltip({
|
||||
placement: 'bottom'
|
||||
});
|
||||
|
||||
if (check.operator === 'matches' || check.operator === '!matches') {
|
||||
if (this._validateRegex(check.value)) {
|
||||
$(element).removeClass('invalid-input');
|
||||
} else {
|
||||
$(element).addClass('invalid-input');
|
||||
}
|
||||
} else {
|
||||
var self = this,
|
||||
data = [
|
||||
{
|
||||
text: t('workflowengine', 'Sync clients'),
|
||||
children: [
|
||||
{id: 'android', text: t('workflowengine', 'Android client')},
|
||||
{id: 'ios', text: t('workflowengine', 'iOS client')},
|
||||
{id: 'desktop', text: t('workflowengine', 'Desktop client')},
|
||||
{id: 'mail', text: t('workflowengine', 'Thunderbird & Outlook addons')}
|
||||
]
|
||||
}
|
||||
];
|
||||
if (this.predefinedValues.indexOf(check.value) === -1) {
|
||||
data.unshift({
|
||||
id: check.value,
|
||||
text: check.value
|
||||
});
|
||||
}
|
||||
|
||||
$(element).select2({
|
||||
data: data,
|
||||
createSearchChoice: function(term) {
|
||||
if (self.predefinedValues.indexOf(check.value) === -1) {
|
||||
return {
|
||||
id: term,
|
||||
text: term
|
||||
};
|
||||
}
|
||||
},
|
||||
id: function(element) {
|
||||
return element.id;
|
||||
},
|
||||
formatResult: function (tag) {
|
||||
return tag.text;
|
||||
},
|
||||
formatSelection: function (tag) {
|
||||
return tag.text;
|
||||
},
|
||||
escapeMarkup: function(m) {
|
||||
return m;
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
_validateRegex: function(string) {
|
||||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
|
||||
result = regexRegex.exec(string);
|
||||
return result !== null;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestUserAgentPlugin);
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import axios from 'nextcloud-axios'
|
||||
import { getApiUrl } from './helpers/api'
|
||||
import confirmPassword from 'nextcloud-password-confirmation'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
rules: [],
|
||||
scope: OCP.InitialState.loadState('workflowengine', 'scope'),
|
||||
operations: OCP.InitialState.loadState('workflowengine', 'operators'),
|
||||
|
||||
plugins: Vue.observable({
|
||||
checks: {},
|
||||
operators: {}
|
||||
}),
|
||||
|
||||
entities: OCP.InitialState.loadState('workflowengine', 'entities'),
|
||||
events: OCP.InitialState.loadState('workflowengine', 'entities')
|
||||
.map((entity) => entity.events.map(event => {
|
||||
return {
|
||||
id: `${entity.id}::${event.eventName}`,
|
||||
entity,
|
||||
...event
|
||||
}
|
||||
})).flat(),
|
||||
checks: OCP.InitialState.loadState('workflowengine', 'checks')
|
||||
},
|
||||
mutations: {
|
||||
addRule(state, rule) {
|
||||
state.rules.push({ ...rule, valid: true })
|
||||
},
|
||||
updateRule(state, rule) {
|
||||
const index = state.rules.findIndex((item) => rule.id === item.id)
|
||||
const newRule = Object.assign({}, rule)
|
||||
Vue.set(state.rules, index, newRule)
|
||||
},
|
||||
removeRule(state, rule) {
|
||||
const index = state.rules.findIndex((item) => rule.id === item.id)
|
||||
state.rules.splice(index, 1)
|
||||
},
|
||||
addPluginCheck(state, plugin) {
|
||||
Vue.set(state.plugins.checks, plugin.class, plugin)
|
||||
},
|
||||
addPluginOperator(state, plugin) {
|
||||
plugin = Object.assign(
|
||||
{ color: 'var(--color-primary-element)' },
|
||||
plugin, state.operations[plugin.id] || {})
|
||||
Vue.set(state.operations, plugin.id, plugin)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async fetchRules(context) {
|
||||
const { data } = await axios.get(getApiUrl(''))
|
||||
Object.values(data.ocs.data).flat().forEach((rule) => {
|
||||
context.commit('addRule', rule)
|
||||
})
|
||||
},
|
||||
createNewRule(context, rule) {
|
||||
let entity = null
|
||||
let events = []
|
||||
if (rule.isComplex === false && rule.fixedEntity === '') {
|
||||
entity = context.state.entities.find((item) => rule.entities && rule.entities[0] === item.id)
|
||||
entity = entity || Object.values(context.state.entities)[0]
|
||||
events = [entity.events[0].eventName]
|
||||
}
|
||||
|
||||
context.commit('addRule', {
|
||||
id: -(new Date().getTime()),
|
||||
class: rule.id,
|
||||
entity: entity ? entity.id : rule.fixedEntity,
|
||||
events,
|
||||
name: '', // unused in the new ui, there for legacy reasons
|
||||
checks: [],
|
||||
operation: rule.operation || ''
|
||||
})
|
||||
},
|
||||
updateRule(context, rule) {
|
||||
context.commit('updateRule', {
|
||||
...rule,
|
||||
events: typeof rule.events === 'string' ? JSON.parse(rule.events) : rule.events
|
||||
})
|
||||
},
|
||||
removeRule(context, rule) {
|
||||
context.commit('removeRule', rule)
|
||||
},
|
||||
async pushUpdateRule(context, rule) {
|
||||
await confirmPassword()
|
||||
let result
|
||||
if (rule.id < 0) {
|
||||
result = await axios.post(getApiUrl(''), rule)
|
||||
} else {
|
||||
result = await axios.put(getApiUrl(`/${rule.id}`), rule)
|
||||
}
|
||||
Vue.set(rule, 'id', result.data.ocs.data.id)
|
||||
context.commit('updateRule', rule)
|
||||
},
|
||||
async deleteRule(context, rule) {
|
||||
await confirmPassword()
|
||||
await axios.delete(getApiUrl(`/${rule.id}`))
|
||||
context.commit('removeRule', rule)
|
||||
},
|
||||
setValid(context, { rule, valid }) {
|
||||
rule.valid = valid
|
||||
context.commit('updateRule', rule)
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getRules(state) {
|
||||
return state.rules.sort((rule1, rule2) => {
|
||||
return rule1.id - rule2.id || rule2.class - rule1.class
|
||||
})
|
||||
},
|
||||
getOperationForRule(state) {
|
||||
return (rule) => state.operations[rule.class]
|
||||
},
|
||||
getEntityForOperation(state) {
|
||||
return (operation) => state.entities.find((entity) => operation.fixedEntity === entity.id)
|
||||
},
|
||||
getEventsForOperation(state) {
|
||||
return (operation) => state.events
|
||||
},
|
||||
/**
|
||||
* Return all available checker plugins for a given entity class
|
||||
*/
|
||||
getChecksForEntity(state) {
|
||||
return (entity) => {
|
||||
return state.checks
|
||||
.filter((check) => check.supportedEntities.indexOf(entity) > -1 || check.supportedEntities.length === 0)
|
||||
.map((check) => state.plugins.checks[check.id])
|
||||
.reduce((obj, item) => {
|
||||
obj[item.class] = item
|
||||
return obj
|
||||
}, {})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default store
|
|
@ -1,45 +0,0 @@
|
|||
<div class="operation{{#if hasChanged}} modified{{/if}}">
|
||||
<div class="operation-header">
|
||||
<input type="text" class="operation-name" placeholder="{{shortRuleDescTXT}}" value="{{operation.name}}" />
|
||||
<input type="text" class="operation-operation" value="{{operation.operation}}" />
|
||||
{{! delete only makes sense if the operation is already saved }}
|
||||
{{#if operation.id}}
|
||||
<span class="button-delete icon-delete"></span>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="checks">
|
||||
{{#each operation.checks}}
|
||||
<div class="check" data-id="{{@index}}">
|
||||
<select class="check-class">
|
||||
{{#each ../classes}}
|
||||
<option value="{{class}}" {{{selectItem class ../class}}}>{{name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<select class="check-operator">
|
||||
{{#each (getOperators class)}}
|
||||
<option value="{{operator}}" {{{selectItem operator ../operator}}}>{{name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<input type="text" class="check-value" value="{{value}}">
|
||||
<span class="button-delete-check icon-delete"></span>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<button class="button-add">{{addRuleTXT}}</button>
|
||||
{{#if hasChanged}}
|
||||
{{! reset only makes sense if the operation is already saved }}
|
||||
{{#if operation.id}}
|
||||
<button class="button-reset pull-right">{{resetTXT}}</button>
|
||||
{{/if}}
|
||||
<button class="button-save pull-right">{{saveTXT}}</button>
|
||||
{{/if}}
|
||||
{{#if saving}}
|
||||
<span class="icon-loading-small pull-right"></span>
|
||||
<span class="pull-right">{{savingTXT}}</span>
|
||||
{{else}}{{#if message}}
|
||||
<span class="msg pull-right {{#if errorMessage}}error{{else}}success{{/if}}">
|
||||
{{message}}{{#if errorMessage}} {{errorMessage}}{{/if}}
|
||||
</span>
|
||||
{{/if}}{{/if}}
|
||||
</div>
|
|
@ -1,2 +0,0 @@
|
|||
<div class="operations"></div>
|
||||
<button class="button-add-operation">{{addRuleGroupTXT}}</button>
|
|
@ -1,75 +0,0 @@
|
|||
/**
|
||||
* @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 = {
|
||||
getCheck: function() {
|
||||
return {
|
||||
'class': 'OCA\\WorkflowEngine\\Check\\UserGroupMembership',
|
||||
'name': t('workflowengine', 'User group membership'),
|
||||
'operators': [
|
||||
{'operator': 'is', 'name': t('workflowengine', 'is member of')},
|
||||
{'operator': '!is', 'name': t('workflowengine', 'is not member of')}
|
||||
]
|
||||
};
|
||||
},
|
||||
render: function(element, check, groups) {
|
||||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\UserGroupMembership') {
|
||||
return;
|
||||
}
|
||||
|
||||
$(element).css('width', '400px');
|
||||
|
||||
$(element).select2({
|
||||
data: { results: groups, text: 'displayname' },
|
||||
initSelection: function (element, callback) {
|
||||
var groupId = element.val();
|
||||
if (groupId && groups.length > 0) {
|
||||
callback({
|
||||
id: groupId,
|
||||
displayname: groups.find(function (group) {
|
||||
return group.id === groupId;
|
||||
}).displayname
|
||||
});
|
||||
} else if (groupId) {
|
||||
callback({
|
||||
id: groupId,
|
||||
displayname: groupId
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
formatResult: function (element) {
|
||||
return '<span>' + escapeHTML(element.displayname) + '</span>';
|
||||
},
|
||||
formatSelection: function (element) {
|
||||
return '<span title="'+escapeHTML(element.id)+'">'+escapeHTML(element.displayname)+'</span>';
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin);
|
|
@ -1,12 +1,70 @@
|
|||
import './admin'
|
||||
import './filemimetypeplugin'
|
||||
import './filenameplugin'
|
||||
import './filesizeplugin'
|
||||
import './filesystemtagsplugin'
|
||||
import './requestremoteaddressplugin'
|
||||
import './requesttimeplugin'
|
||||
import './requesturlplugin'
|
||||
import './requestuseragentplugin'
|
||||
import './usergroupmembershipplugin'
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import store from './store'
|
||||
import Settings from './components/Workflow'
|
||||
import ShippedChecks from './components/Checks'
|
||||
|
||||
window.OCA.WorkflowEngine = OCA.WorkflowEngine
|
||||
/**
|
||||
* A plugin for displaying a custom value field for checks
|
||||
*
|
||||
* @typedef {Object} CheckPlugin
|
||||
* @property {string} class - The PHP class name of the check
|
||||
* @property {Comparison[]} operators - A list of possible comparison operations running on the check
|
||||
* @property {Vue} component - A vue component to handle the rendering of options
|
||||
* The component should handle the v-model directive properly,
|
||||
* so it needs a value property to receive data and emit an input
|
||||
* event once the data has changed
|
||||
* @property {callable} placeholder - Return a placeholder of no custom component is used
|
||||
* @property {callable} validate - validate a check if no custom component is used
|
||||
**/
|
||||
|
||||
/**
|
||||
* A plugin for extending the admin page repesentation of a operator
|
||||
*
|
||||
* @typedef {Object} OperatorPlugin
|
||||
* @property {string} id - The PHP class name of the check
|
||||
* @property {string} operation - Default value for the operation field
|
||||
* @property {string} color - Custom color code to be applied for the operator selector
|
||||
* @property {Vue} component - A vue component to handle the rendering of options
|
||||
* The component should handle the v-model directive properly,
|
||||
* so it needs a value property to receive data and emit an input
|
||||
* event once the data has changed
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Comparison
|
||||
* @property {string} operator - value the comparison should have, e.g. !less, greater
|
||||
* @property {string} name - Translated readable text, e.g. less or equals
|
||||
**/
|
||||
|
||||
/**
|
||||
* Public javascript api for apps to register custom plugins
|
||||
*/
|
||||
window.OCA.WorkflowEngine = Object.assign({}, OCA.WorkflowEngine, {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CheckPlugin} Plugin
|
||||
*/
|
||||
registerCheck: function (Plugin) {
|
||||
store.commit('addPluginCheck', Plugin)
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {OperatorPlugin} Plugin
|
||||
*/
|
||||
registerOperator: function (Plugin) {
|
||||
store.commit('addPluginOperator', Plugin)
|
||||
}
|
||||
})
|
||||
|
||||
// Register shipped checks
|
||||
ShippedChecks.forEach((checkPlugin) => window.OCA.WorkflowEngine.registerCheck(checkPlugin))
|
||||
|
||||
Vue.use(Vuex)
|
||||
Vue.prototype.t = t
|
||||
|
||||
const View = Vue.extend(Settings)
|
||||
new View({
|
||||
store
|
||||
}).$mount('#workflowengine')
|
||||
|
|
|
@ -22,22 +22,4 @@
|
|||
/** @var array $_ */
|
||||
/** @var \OCP\IL10N $l */
|
||||
?>
|
||||
<div id="<?php p($_['appid']); ?>" class="section workflowengine">
|
||||
<h2 class="inlineblock"><?php p($_['heading']); ?></h2>
|
||||
<?php if (!empty($_['docs'])): ?>
|
||||
<a target="_blank" rel="noreferrer noopener" class="icon-info svg"
|
||||
title="<?php p($l->t('Open documentation'));?>"
|
||||
href="<?php p(link_to_docs($_['docs'])); ?>">
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($_['settings-hint'])): ?>
|
||||
<p class="settings-hint"><?php p($_['settings-hint']); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($_['description'])): ?>
|
||||
<p><?php p($_['description']); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="rules"><span class="icon-loading-small"></span> <?php p($l->t('Loading…')); ?></div>
|
||||
</div>
|
||||
<div id="<?php p(\OCA\WorkflowEngine\AppInfo\Application::APP_ID); ?>"></div>
|
|
@ -22,10 +22,23 @@
|
|||
namespace OCA\WorkflowEngine\Tests;
|
||||
|
||||
|
||||
use OC\L10N\L10N;
|
||||
use OCA\WorkflowEngine\Entity\File;
|
||||
use OCA\WorkflowEngine\Helper\ScopeContext;
|
||||
use OCA\WorkflowEngine\Manager;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
use OCP\ILogger;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserSession;
|
||||
use OCP\WorkflowEngine\ICheck;
|
||||
use OCP\WorkflowEngine\IEntity;
|
||||
use OCP\WorkflowEngine\IManager;
|
||||
use OCP\WorkflowEngine\IOperation;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
/**
|
||||
|
@ -38,37 +51,75 @@ class ManagerTest extends TestCase {
|
|||
|
||||
/** @var Manager */
|
||||
protected $manager;
|
||||
/** @var IDBConnection */
|
||||
/** @var MockObject|IDBConnection */
|
||||
protected $db;
|
||||
/** @var \PHPUnit\Framework\MockObject\MockObject|ILogger */
|
||||
protected $logger;
|
||||
/** @var \PHPUnit\Framework\MockObject\MockObject|EventDispatcherInterface */
|
||||
protected $eventDispatcher;
|
||||
/** @var MockObject|IServerContainer */
|
||||
protected $container;
|
||||
/** @var MockObject|IUserSession */
|
||||
protected $session;
|
||||
/** @var MockObject|L10N */
|
||||
protected $l;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->db = \OC::$server->getDatabaseConnection();
|
||||
$container = $this->createMock(IServerContainer::class);
|
||||
$l = $this->createMock(IL10N::class);
|
||||
$l->method('t')
|
||||
$this->container = $this->createMock(IServerContainer::class);
|
||||
/** @var IL10N|MockObject $l */
|
||||
$this->l = $this->createMock(IL10N::class);
|
||||
$this->l->method('t')
|
||||
->will($this->returnCallback(function($text, $parameters = []) {
|
||||
return vsprintf($text, $parameters);
|
||||
}));
|
||||
|
||||
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
$this->logger = $this->createMock(ILogger::class);
|
||||
$this->session = $this->createMock(IUserSession::class);
|
||||
|
||||
$this->manager = new Manager(
|
||||
\OC::$server->getDatabaseConnection(),
|
||||
$container,
|
||||
$l
|
||||
$this->container,
|
||||
$this->l,
|
||||
$this->eventDispatcher,
|
||||
$this->logger,
|
||||
$this->session
|
||||
);
|
||||
$this->clearChecks();
|
||||
$this->clearTables();
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
$this->clearChecks();
|
||||
$this->clearTables();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function clearChecks() {
|
||||
/**
|
||||
* @return MockObject|ScopeContext
|
||||
*/
|
||||
protected function buildScope(string $scopeId = null): MockObject {
|
||||
$scopeContext = $this->createMock(ScopeContext::class);
|
||||
$scopeContext->expects($this->any())
|
||||
->method('getScope')
|
||||
->willReturn($scopeId ? IManager::SCOPE_USER : IManager::SCOPE_ADMIN);
|
||||
$scopeContext->expects($this->any())
|
||||
->method('getScopeId')
|
||||
->willReturn($scopeId ?? '');
|
||||
$scopeContext->expects($this->any())
|
||||
->method('getHash')
|
||||
->willReturn(md5($scopeId ?? ''));
|
||||
|
||||
return $scopeContext;
|
||||
}
|
||||
|
||||
public function clearTables() {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->delete('flow_checks')
|
||||
->execute();
|
||||
foreach(['flow_checks', 'flow_operations', 'flow_operations_scope'] as $table) {
|
||||
$query->delete($table)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
public function testChecks() {
|
||||
|
@ -91,4 +142,276 @@ class ManagerTest extends TestCase {
|
|||
$this->assertArrayNotHasKey($check1, $data);
|
||||
$this->assertArrayHasKey($check2, $data);
|
||||
}
|
||||
|
||||
public function testScope() {
|
||||
$adminScope = $this->buildScope();
|
||||
$userScope = $this->buildScope('jackie');
|
||||
$entity = File::class;
|
||||
|
||||
$opId1 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestOp', 'Test01', [11, 22], 'foo', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]);
|
||||
|
||||
$opId2 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestOp', 'Test02', [33, 22], 'bar', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]);
|
||||
$opId3 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestOp', 'Test03', [11, 44], 'foobar', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId3, $userScope]);
|
||||
|
||||
$this->assertTrue($this->invokePrivate($this->manager, 'canModify', [$opId1, $adminScope]));
|
||||
$this->assertFalse($this->invokePrivate($this->manager, 'canModify', [$opId2, $adminScope]));
|
||||
$this->assertFalse($this->invokePrivate($this->manager, 'canModify', [$opId3, $adminScope]));
|
||||
|
||||
$this->assertFalse($this->invokePrivate($this->manager, 'canModify', [$opId1, $userScope]));
|
||||
$this->assertTrue($this->invokePrivate($this->manager, 'canModify', [$opId2, $userScope]));
|
||||
$this->assertTrue($this->invokePrivate($this->manager, 'canModify', [$opId3, $userScope]));
|
||||
}
|
||||
|
||||
public function testGetAllOperations() {
|
||||
$adminScope = $this->buildScope();
|
||||
$userScope = $this->buildScope('jackie');
|
||||
$entity = File::class;
|
||||
|
||||
$opId1 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]);
|
||||
|
||||
$opId2 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]);
|
||||
$opId3 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestUserOp', 'Test03', [11, 44], 'foobar', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId3, $userScope]);
|
||||
|
||||
$adminOps = $this->manager->getAllOperations($adminScope);
|
||||
$userOps = $this->manager->getAllOperations($userScope);
|
||||
|
||||
$this->assertSame(1, count($adminOps));
|
||||
$this->assertTrue(array_key_exists('OCA\WFE\TestAdminOp', $adminOps));
|
||||
$this->assertFalse(array_key_exists('OCA\WFE\TestUserOp', $adminOps));
|
||||
|
||||
$this->assertSame(1, count($userOps));
|
||||
$this->assertFalse(array_key_exists('OCA\WFE\TestAdminOp', $userOps));
|
||||
$this->assertTrue(array_key_exists('OCA\WFE\TestUserOp', $userOps));
|
||||
$this->assertSame(2, count($userOps['OCA\WFE\TestUserOp']));
|
||||
}
|
||||
|
||||
public function testGetOperations() {
|
||||
$adminScope = $this->buildScope();
|
||||
$userScope = $this->buildScope('jackie');
|
||||
$entity = File::class;
|
||||
|
||||
$opId1 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestOp', 'Test01', [11, 22], 'foo', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]);
|
||||
$opId4 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\OtherTestOp', 'Test04', [5], 'foo', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId4, $adminScope]);
|
||||
|
||||
$opId2 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestOp', 'Test02', [33, 22], 'bar', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]);
|
||||
$opId3 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestOp', 'Test03', [11, 44], 'foobar', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId3, $userScope]);
|
||||
$opId5 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\OtherTestOp', 'Test05', [5], 'foobar', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId5, $userScope]);
|
||||
|
||||
$adminOps = $this->manager->getOperations('OCA\WFE\TestOp', $adminScope);
|
||||
$userOps = $this->manager->getOperations('OCA\WFE\TestOp', $userScope);
|
||||
|
||||
$this->assertSame(1, count($adminOps));
|
||||
array_walk($adminOps, function ($op) {
|
||||
$this->assertTrue($op['class'] === 'OCA\WFE\TestOp');
|
||||
});
|
||||
|
||||
$this->assertSame(2, count($userOps));
|
||||
array_walk($userOps, function ($op) {
|
||||
$this->assertTrue($op['class'] === 'OCA\WFE\TestOp');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public function testUpdateOperation() {
|
||||
$adminScope = $this->buildScope();
|
||||
$userScope = $this->buildScope('jackie');
|
||||
$entity = File::class;
|
||||
|
||||
$this->container->expects($this->any())
|
||||
->method('query')
|
||||
->willReturnCallback(function ($class) {
|
||||
if(substr($class, -2) === 'Op') {
|
||||
return $this->createMock(IOperation::class);
|
||||
} else if($class === File::class) {
|
||||
return $this->getMockBuilder(File::class)
|
||||
->setConstructorArgs([$this->l, $this->createMock(IURLGenerator::class), $this->createMock(IRootFolder::class)])
|
||||
->setMethodsExcept(['getEvents'])
|
||||
->getMock();
|
||||
}
|
||||
return $this->createMock(ICheck::class);
|
||||
});
|
||||
|
||||
$opId1 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]);
|
||||
|
||||
$opId2 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]);
|
||||
|
||||
$check1 = ['class' => 'OCA\WFE\C22', 'operator' => 'eq', 'value' => 'asdf'];
|
||||
$check2 = ['class' => 'OCA\WFE\C33', 'operator' => 'eq', 'value' => 23456];
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$op = $this->manager->updateOperation($opId1, 'Test01a', [$check1, $check2], 'foohur', $adminScope, $entity, ['\OCP\Files::postDelete']);
|
||||
$this->assertSame('Test01a', $op['name']);
|
||||
$this->assertSame('foohur', $op['operation']);
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$op = $this->manager->updateOperation($opId2, 'Test02a', [$check1], 'barfoo', $userScope, $entity, ['\OCP\Files::postDelete']);
|
||||
$this->assertSame('Test02a', $op['name']);
|
||||
$this->assertSame('barfoo', $op['operation']);
|
||||
|
||||
foreach([[$adminScope, $opId2], [$userScope, $opId1]] as $run) {
|
||||
try {
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$this->manager->updateOperation($run[1], 'Evil', [$check2], 'hackx0r', $run[0], $entity, []);
|
||||
$this->assertTrue(false, 'DomainException not thrown');
|
||||
} catch (\DomainException $e) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testDeleteOperation() {
|
||||
$adminScope = $this->buildScope();
|
||||
$userScope = $this->buildScope('jackie');
|
||||
$entity = File::class;
|
||||
|
||||
$opId1 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestAdminOp', 'Test01', [11, 22], 'foo', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId1, $adminScope]);
|
||||
|
||||
$opId2 = $this->invokePrivate(
|
||||
$this->manager,
|
||||
'insertOperation',
|
||||
['OCA\WFE\TestUserOp', 'Test02', [33, 22], 'bar', $entity, []]
|
||||
);
|
||||
$this->invokePrivate($this->manager, 'addScope', [$opId2, $userScope]);
|
||||
|
||||
foreach([[$adminScope, $opId2], [$userScope, $opId1]] as $run) {
|
||||
try {
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$this->manager->deleteOperation($run[1], $run[0]);
|
||||
$this->assertTrue(false, 'DomainException not thrown');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf(\DomainException::class, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$this->manager->deleteOperation($opId1, $adminScope);
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$this->manager->deleteOperation($opId2, $userScope);
|
||||
|
||||
foreach([$opId1, $opId2] as $opId) {
|
||||
try {
|
||||
$this->invokePrivate($this->manager, 'getOperation', [$opId]);
|
||||
$this->assertTrue(false, 'UnexpectedValueException not thrown');
|
||||
} catch(\Exception $e) {
|
||||
$this->assertInstanceOf(\UnexpectedValueException::class, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetEntitiesListBuildInOnly() {
|
||||
$fileEntityMock = $this->createMock(File::class);
|
||||
|
||||
$this->container->expects($this->once())
|
||||
->method('query')
|
||||
->with(File::class)
|
||||
->willReturn($fileEntityMock);
|
||||
|
||||
$entities = $this->manager->getEntitiesList();
|
||||
|
||||
$this->assertCount(1, $entities);
|
||||
$this->assertInstanceOf(IEntity::class, $entities[0]);
|
||||
}
|
||||
|
||||
public function testGetEntitiesList() {
|
||||
$fileEntityMock = $this->createMock(File::class);
|
||||
|
||||
$this->container->expects($this->once())
|
||||
->method('query')
|
||||
->with(File::class)
|
||||
->willReturn($fileEntityMock);
|
||||
|
||||
/** @var MockObject|IEntity $extraEntity */
|
||||
$extraEntity = $this->createMock(IEntity::class);
|
||||
|
||||
$this->eventDispatcher->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with('OCP\WorkflowEngine::registerEntities', $this->anything())
|
||||
->willReturnCallback(function() use ($extraEntity) {
|
||||
$this->manager->registerEntity($extraEntity);
|
||||
});
|
||||
|
||||
$entities = $this->manager->getEntitiesList();
|
||||
|
||||
$this->assertCount(2, $entities);
|
||||
|
||||
$entityTypeCounts = array_reduce($entities, function (array $carry, IEntity $entity) {
|
||||
if($entity instanceof File) $carry[0]++;
|
||||
else if($entity instanceof IEntity) $carry[1]++;
|
||||
return $carry;
|
||||
}, [0, 0]);
|
||||
|
||||
$this->assertSame(1, $entityTypeCounts[0]);
|
||||
$this->assertSame(1, $entityTypeCounts[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,17 +7,5 @@ module.exports = {
|
|||
publicPath: '/js/',
|
||||
filename: 'workflowengine.js',
|
||||
jsonpFunction: 'webpackJsonpWorkflowengine'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.handlebars/,
|
||||
loader: "handlebars-loader",
|
||||
query: {
|
||||
extensions: '.handlebars',
|
||||
helperDirs: path.join(__dirname, 'src/hbs_helpers'),
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -438,9 +438,17 @@ return array(
|
|||
'OCP\\User\\Backend\\ISetDisplayNameBackend' => $baseDir . '/lib/public/User/Backend/ISetDisplayNameBackend.php',
|
||||
'OCP\\User\\Backend\\ISetPasswordBackend' => $baseDir . '/lib/public/User/Backend/ISetPasswordBackend.php',
|
||||
'OCP\\Util' => $baseDir . '/lib/public/Util.php',
|
||||
'OCP\\WorkflowEngine\\GenericEntityEvent' => $baseDir . '/lib/public/WorkflowEngine/GenericEntityEvent.php',
|
||||
'OCP\\WorkflowEngine\\ICheck' => $baseDir . '/lib/public/WorkflowEngine/ICheck.php',
|
||||
'OCP\\WorkflowEngine\\IComplexOperation' => $baseDir . '/lib/public/WorkflowEngine/IComplexOperation.php',
|
||||
'OCP\\WorkflowEngine\\IEntity' => $baseDir . '/lib/public/WorkflowEngine/IEntity.php',
|
||||
'OCP\\WorkflowEngine\\IEntityCheck' => $baseDir . '/lib/public/WorkflowEngine/IEntityCheck.php',
|
||||
'OCP\\WorkflowEngine\\IEntityEvent' => $baseDir . '/lib/public/WorkflowEngine/IEntityEvent.php',
|
||||
'OCP\\WorkflowEngine\\IFileCheck' => $baseDir . '/lib/public/WorkflowEngine/IFileCheck.php',
|
||||
'OCP\\WorkflowEngine\\IManager' => $baseDir . '/lib/public/WorkflowEngine/IManager.php',
|
||||
'OCP\\WorkflowEngine\\IOperation' => $baseDir . '/lib/public/WorkflowEngine/IOperation.php',
|
||||
'OCP\\WorkflowEngine\\IRuleMatcher' => $baseDir . '/lib/public/WorkflowEngine/IRuleMatcher.php',
|
||||
'OCP\\WorkflowEngine\\ISpecificOperation' => $baseDir . '/lib/public/WorkflowEngine/ISpecificOperation.php',
|
||||
'OC\\Accounts\\Account' => $baseDir . '/lib/private/Accounts/Account.php',
|
||||
'OC\\Accounts\\AccountManager' => $baseDir . '/lib/private/Accounts/AccountManager.php',
|
||||
'OC\\Accounts\\AccountProperty' => $baseDir . '/lib/private/Accounts/AccountProperty.php',
|
||||
|
|
|
@ -472,9 +472,17 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OCP\\User\\Backend\\ISetDisplayNameBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetDisplayNameBackend.php',
|
||||
'OCP\\User\\Backend\\ISetPasswordBackend' => __DIR__ . '/../../..' . '/lib/public/User/Backend/ISetPasswordBackend.php',
|
||||
'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php',
|
||||
'OCP\\WorkflowEngine\\GenericEntityEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/GenericEntityEvent.php',
|
||||
'OCP\\WorkflowEngine\\ICheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ICheck.php',
|
||||
'OCP\\WorkflowEngine\\IComplexOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IComplexOperation.php',
|
||||
'OCP\\WorkflowEngine\\IEntity' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntity.php',
|
||||
'OCP\\WorkflowEngine\\IEntityCheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityCheck.php',
|
||||
'OCP\\WorkflowEngine\\IEntityEvent' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IEntityEvent.php',
|
||||
'OCP\\WorkflowEngine\\IFileCheck' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IFileCheck.php',
|
||||
'OCP\\WorkflowEngine\\IManager' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IManager.php',
|
||||
'OCP\\WorkflowEngine\\IOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IOperation.php',
|
||||
'OCP\\WorkflowEngine\\IRuleMatcher' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/IRuleMatcher.php',
|
||||
'OCP\\WorkflowEngine\\ISpecificOperation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/ISpecificOperation.php',
|
||||
'OC\\Accounts\\Account' => __DIR__ . '/../../..' . '/lib/private/Accounts/Account.php',
|
||||
'OC\\Accounts\\AccountManager' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountManager.php',
|
||||
'OC\\Accounts\\AccountProperty' => __DIR__ . '/../../..' . '/lib/private/Accounts/AccountProperty.php',
|
||||
|
|
|
@ -119,7 +119,7 @@ class File extends Node implements \OCP\Files\File {
|
|||
$fileInfo = $this->getFileInfo();
|
||||
$this->view->unlink($this->path);
|
||||
$nonExisting = new NonExistingFile($this->root, $this->view, $this->path, $fileInfo);
|
||||
$this->root->emit('\OC\Files', 'postDelete', array($nonExisting));
|
||||
$this->sendHooks(['postDelete'], [$nonExisting]);
|
||||
$this->exists = false;
|
||||
$this->fileInfo = null;
|
||||
} else {
|
||||
|
|
|
@ -156,14 +156,12 @@ class Folder extends Node implements \OCP\Files\Folder {
|
|||
if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
|
||||
$fullPath = $this->getFullPath($path);
|
||||
$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
|
||||
$this->root->emit('\OC\Files', 'preWrite', array($nonExisting));
|
||||
$this->root->emit('\OC\Files', 'preCreate', array($nonExisting));
|
||||
$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
|
||||
if(!$this->view->mkdir($fullPath)) {
|
||||
throw new NotPermittedException('Could not create folder');
|
||||
}
|
||||
$node = new Folder($this->root, $this->view, $fullPath);
|
||||
$this->root->emit('\OC\Files', 'postWrite', array($node));
|
||||
$this->root->emit('\OC\Files', 'postCreate', array($node));
|
||||
$this->sendHooks(['postWrite', 'postCreate'], [$node]);
|
||||
return $node;
|
||||
} else {
|
||||
throw new NotPermittedException('No create permission for folder');
|
||||
|
@ -179,14 +177,12 @@ class Folder extends Node implements \OCP\Files\Folder {
|
|||
if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
|
||||
$fullPath = $this->getFullPath($path);
|
||||
$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
|
||||
$this->root->emit('\OC\Files', 'preWrite', array($nonExisting));
|
||||
$this->root->emit('\OC\Files', 'preCreate', array($nonExisting));
|
||||
$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
|
||||
if (!$this->view->touch($fullPath)) {
|
||||
throw new NotPermittedException('Could not create path');
|
||||
}
|
||||
$node = new File($this->root, $this->view, $fullPath);
|
||||
$this->root->emit('\OC\Files', 'postWrite', array($node));
|
||||
$this->root->emit('\OC\Files', 'postCreate', array($node));
|
||||
$this->sendHooks(['postWrite', 'postCreate'], [$node]);
|
||||
return $node;
|
||||
}
|
||||
throw new NotPermittedException('No create permission for path');
|
||||
|
@ -341,7 +337,7 @@ class Folder extends Node implements \OCP\Files\Folder {
|
|||
$fileInfo = $this->getFileInfo();
|
||||
$this->view->rmdir($this->path);
|
||||
$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
|
||||
$this->root->emit('\OC\Files', 'postDelete', array($nonExisting));
|
||||
$this->sendHooks(['postDelete'], [$nonExisting]);
|
||||
$this->exists = false;
|
||||
} else {
|
||||
throw new NotPermittedException('No delete permission for path');
|
||||
|
|
|
@ -26,6 +26,8 @@ use OCP\Files\FileInfo;
|
|||
use OC\Files\Filesystem;
|
||||
use OC\Files\View;
|
||||
use OCP\Util;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
|
||||
class HookConnector {
|
||||
/**
|
||||
|
@ -42,6 +44,8 @@ class HookConnector {
|
|||
* @var FileInfo[]
|
||||
*/
|
||||
private $deleteMetaCache = [];
|
||||
/** @var EventDispatcherInterface */
|
||||
private $dispatcher;
|
||||
|
||||
/**
|
||||
* HookConnector constructor.
|
||||
|
@ -49,9 +53,10 @@ class HookConnector {
|
|||
* @param Root $root
|
||||
* @param View $view
|
||||
*/
|
||||
public function __construct(Root $root, View $view) {
|
||||
public function __construct(Root $root, View $view, EventDispatcherInterface $dispatcher) {
|
||||
$this->root = $root;
|
||||
$this->view = $view;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
public function viewToNode() {
|
||||
|
@ -79,72 +84,85 @@ class HookConnector {
|
|||
public function write($arguments) {
|
||||
$node = $this->getNodeForPath($arguments['path']);
|
||||
$this->root->emit('\OC\Files', 'preWrite', [$node]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::preWrite', new GenericEvent($node));
|
||||
}
|
||||
|
||||
public function postWrite($arguments) {
|
||||
$node = $this->getNodeForPath($arguments['path']);
|
||||
$this->root->emit('\OC\Files', 'postWrite', [$node]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::postWrite', new GenericEvent($node));
|
||||
}
|
||||
|
||||
public function create($arguments) {
|
||||
$node = $this->getNodeForPath($arguments['path']);
|
||||
$this->root->emit('\OC\Files', 'preCreate', [$node]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::preCreate', new GenericEvent($node));
|
||||
}
|
||||
|
||||
public function postCreate($arguments) {
|
||||
$node = $this->getNodeForPath($arguments['path']);
|
||||
$this->root->emit('\OC\Files', 'postCreate', [$node]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::postCreate', new GenericEvent($node));
|
||||
}
|
||||
|
||||
public function delete($arguments) {
|
||||
$node = $this->getNodeForPath($arguments['path']);
|
||||
$this->deleteMetaCache[$node->getPath()] = $node->getFileInfo();
|
||||
$this->root->emit('\OC\Files', 'preDelete', [$node]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::preDelete', new GenericEvent($node));
|
||||
}
|
||||
|
||||
public function postDelete($arguments) {
|
||||
$node = $this->getNodeForPath($arguments['path']);
|
||||
unset($this->deleteMetaCache[$node->getPath()]);
|
||||
$this->root->emit('\OC\Files', 'postDelete', [$node]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::postDelete', new GenericEvent($node));
|
||||
}
|
||||
|
||||
public function touch($arguments) {
|
||||
$node = $this->getNodeForPath($arguments['path']);
|
||||
$this->root->emit('\OC\Files', 'preTouch', [$node]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::preTouch', new GenericEvent($node));
|
||||
}
|
||||
|
||||
public function postTouch($arguments) {
|
||||
$node = $this->getNodeForPath($arguments['path']);
|
||||
$this->root->emit('\OC\Files', 'postTouch', [$node]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::postTouch', new GenericEvent($node));
|
||||
}
|
||||
|
||||
public function rename($arguments) {
|
||||
$source = $this->getNodeForPath($arguments['oldpath']);
|
||||
$target = $this->getNodeForPath($arguments['newpath']);
|
||||
$this->root->emit('\OC\Files', 'preRename', [$source, $target]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::preRename', new GenericEvent([$source, $target]));
|
||||
}
|
||||
|
||||
public function postRename($arguments) {
|
||||
$source = $this->getNodeForPath($arguments['oldpath']);
|
||||
$target = $this->getNodeForPath($arguments['newpath']);
|
||||
$this->root->emit('\OC\Files', 'postRename', [$source, $target]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::postRename', new GenericEvent([$source, $target]));
|
||||
}
|
||||
|
||||
public function copy($arguments) {
|
||||
$source = $this->getNodeForPath($arguments['oldpath']);
|
||||
$target = $this->getNodeForPath($arguments['newpath']);
|
||||
$this->root->emit('\OC\Files', 'preCopy', [$source, $target]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::preCopy', new GenericEvent([$source, $target]));
|
||||
}
|
||||
|
||||
public function postCopy($arguments) {
|
||||
$source = $this->getNodeForPath($arguments['oldpath']);
|
||||
$target = $this->getNodeForPath($arguments['newpath']);
|
||||
$this->root->emit('\OC\Files', 'postCopy', [$source, $target]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::postCopy', new GenericEvent([$source, $target]));
|
||||
}
|
||||
|
||||
public function read($arguments) {
|
||||
$node = $this->getNodeForPath($arguments['path']);
|
||||
$this->root->emit('\OC\Files', 'read', [$node]);
|
||||
$this->dispatcher->dispatch('\OCP\Files::read', new GenericEvent([$node]));
|
||||
}
|
||||
|
||||
private function getNodeForPath($path) {
|
||||
|
|
|
@ -33,6 +33,7 @@ use OCP\Files\FileInfo;
|
|||
use OCP\Files\InvalidPathException;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
|
||||
// FIXME: this class really should be abstract
|
||||
class Node implements \OCP\Files\Node {
|
||||
|
@ -104,9 +105,12 @@ class Node implements \OCP\Files\Node {
|
|||
/**
|
||||
* @param string[] $hooks
|
||||
*/
|
||||
protected function sendHooks($hooks) {
|
||||
protected function sendHooks($hooks, array $args = null) {
|
||||
$args = !empty($args) ? $args : [$this];
|
||||
$dispatcher = \OC::$server->getEventDispatcher();
|
||||
foreach ($hooks as $hook) {
|
||||
$this->root->emit('\OC\Files', $hook, array($this));
|
||||
$this->root->emit('\OC\Files', $hook, $args);
|
||||
$dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,14 +398,14 @@ class Node implements \OCP\Files\Node {
|
|||
$parent = $this->root->get(dirname($targetPath));
|
||||
if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) {
|
||||
$nonExisting = $this->createNonExistingNode($targetPath);
|
||||
$this->root->emit('\OC\Files', 'preCopy', [$this, $nonExisting]);
|
||||
$this->root->emit('\OC\Files', 'preWrite', [$nonExisting]);
|
||||
$this->sendHooks(['preCopy'], [$this, $nonExisting]);
|
||||
$this->sendHooks(['preWrite'], [$nonExisting]);
|
||||
if (!$this->view->copy($this->path, $targetPath)) {
|
||||
throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath);
|
||||
}
|
||||
$targetNode = $this->root->get($targetPath);
|
||||
$this->root->emit('\OC\Files', 'postCopy', [$this, $targetNode]);
|
||||
$this->root->emit('\OC\Files', 'postWrite', [$targetNode]);
|
||||
$this->sendHooks(['postCopy'], [$this, $targetNode]);
|
||||
$this->sendHooks(['postWrite'], [$targetNode]);
|
||||
return $targetNode;
|
||||
} else {
|
||||
throw new NotPermittedException('No permission to copy to path ' . $targetPath);
|
||||
|
@ -425,14 +429,14 @@ class Node implements \OCP\Files\Node {
|
|||
)
|
||||
) {
|
||||
$nonExisting = $this->createNonExistingNode($targetPath);
|
||||
$this->root->emit('\OC\Files', 'preRename', [$this, $nonExisting]);
|
||||
$this->root->emit('\OC\Files', 'preWrite', [$nonExisting]);
|
||||
$this->sendHooks(['preRename'], [$this, $nonExisting]);
|
||||
$this->sendHooks(['preWrite'], [$nonExisting]);
|
||||
if (!$this->view->rename($this->path, $targetPath)) {
|
||||
throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath);
|
||||
}
|
||||
$targetNode = $this->root->get($targetPath);
|
||||
$this->root->emit('\OC\Files', 'postRename', [$this, $targetNode]);
|
||||
$this->root->emit('\OC\Files', 'postWrite', [$targetNode]);
|
||||
$this->sendHooks(['postRename'], [$this, $targetNode]);
|
||||
$this->sendHooks(['postWrite'], [$targetNode]);
|
||||
$this->path = $targetPath;
|
||||
return $targetNode;
|
||||
} else {
|
||||
|
|
|
@ -298,7 +298,7 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
$this->getLogger(),
|
||||
$this->getUserManager()
|
||||
);
|
||||
$connector = new HookConnector($root, $view);
|
||||
$connector = new HookConnector($root, $view, $c->getEventDispatcher());
|
||||
$connector->viewToNode();
|
||||
|
||||
$previewConnector = new \OC\Preview\WatcherConnector($root, $c->getSystemConfig());
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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;
|
||||
|
||||
/**
|
||||
* Class GenericEntityEvent
|
||||
*
|
||||
* @package OCP\WorkflowEngine
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
class GenericEntityEvent implements IEntityEvent {
|
||||
|
||||
/** @var string */
|
||||
private $displayName;
|
||||
/** @var string */
|
||||
private $eventName;
|
||||
|
||||
/**
|
||||
* GenericEntityEvent constructor.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function __construct(string $displayName, string $eventName) {
|
||||
if(trim($displayName) === '') {
|
||||
throw new \InvalidArgumentException('DisplayName must not be empty');
|
||||
}
|
||||
if(trim($eventName) === '') {
|
||||
throw new \InvalidArgumentException('EventName must not be empty');
|
||||
}
|
||||
|
||||
$this->displayName = trim($displayName);
|
||||
$this->eventName = trim($eventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a translated name to be presented in the web interface.
|
||||
*
|
||||
* Example: "created" (en), "kreita" (eo)
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getDisplayName(): string {
|
||||
return $this->displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the event name that is emitted by the EventDispatcher, e.g.:
|
||||
*
|
||||
* Example: "OCA\MyApp\Factory\Cats::postCreated"
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getEventName(): string {
|
||||
return $this->eventName;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
* @author Morris Jobke <hey@morrisjobke.de>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
|
@ -23,9 +24,6 @@
|
|||
|
||||
namespace OCP\WorkflowEngine;
|
||||
|
||||
|
||||
use OCP\Files\Storage\IStorage;
|
||||
|
||||
/**
|
||||
* Interface ICheck
|
||||
*
|
||||
|
@ -33,13 +31,6 @@ use OCP\Files\Storage\IStorage;
|
|||
* @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
|
||||
|
@ -55,4 +46,28 @@ interface ICheck {
|
|||
* @since 9.1
|
||||
*/
|
||||
public function validateCheck($operator, $value);
|
||||
|
||||
/**
|
||||
* returns a list of Entities the checker supports. The values must match
|
||||
* the class name of the entity.
|
||||
*
|
||||
* An empty result means the check is universally available.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function supportedEntities(): array;
|
||||
|
||||
/**
|
||||
* returns whether the operation can be used in the requested scope.
|
||||
*
|
||||
* Scope IDs are defined as constants in OCP\WorkflowEngine\IManager. At
|
||||
* time of writing these are SCOPE_ADMIN and SCOPE_USER.
|
||||
*
|
||||
* For possibly unknown future scopes the recommended behaviour is: if
|
||||
* user scope is permitted, the default behaviour should return `true`,
|
||||
* otherwise `false`.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function isAvailableForScope(int $scope): bool;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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;
|
||||
|
||||
/**
|
||||
* Interface IComplexOperation
|
||||
*
|
||||
* This interface represents an operator that is less generic and indicates
|
||||
* that some of the tasks it does itself instead of relying on the engine.
|
||||
* This includes:
|
||||
*
|
||||
* * registering listeners – the implementing app needs to ensure that the
|
||||
* business logic registers listeners to the events it listens to. For example
|
||||
* when direct storage access is required, adding a wrapper or listening to
|
||||
* a specific one is required over usual file events.
|
||||
*
|
||||
* @package OCP\WorkflowEngine
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
interface IComplexOperation extends IOperation {
|
||||
|
||||
/**
|
||||
* As IComplexOperation chooses the triggering events itself, a hint has
|
||||
* to be shown to the user so make clear when this operation is becoming
|
||||
* active. This method returns such a translated string.
|
||||
*
|
||||
* Example: "When a file is accessed" (en)
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getTriggerHint(): string;
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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\GenericEvent;
|
||||
|
||||
/**
|
||||
* Interface IEntity
|
||||
*
|
||||
* This interface represents an entity that supports events the workflow engine
|
||||
* can listen to. For example a file with the create, update, etc. events.
|
||||
*
|
||||
* Ensure to listen to 'OCP/WorkflowEngine::loadEntities' for registering your
|
||||
* entities.
|
||||
*
|
||||
* @package OCP\WorkflowEngine
|
||||
* @since 18.0.0
|
||||
*/
|
||||
interface IEntity {
|
||||
|
||||
/**
|
||||
* returns a translated name to be presented in the web interface.
|
||||
*
|
||||
* Example: "File" (en), "Dosiero" (eo)
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* returns the URL to the icon of the entity for display in the web interface.
|
||||
*
|
||||
* Usually, the implementation would utilize the `imagePath()` method of the
|
||||
* `\OCP\IURLGenerator` instance and simply return its result.
|
||||
*
|
||||
* Example implementation: return $this->urlGenerator->imagePath('myApp', 'cat.svg');
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getIcon(): string;
|
||||
|
||||
/**
|
||||
* returns a list of supported events
|
||||
*
|
||||
* @return IEntityEvent[]
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getEvents(): array;
|
||||
|
||||
/**
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function prepareRuleMatcher(IRuleMatcher $ruleMatcher, string $eventName, GenericEvent $event): void;
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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 IFileCheck
|
||||
*
|
||||
* @package OCP\WorkflowEngine
|
||||
* @since 18.0.0
|
||||
*/
|
||||
interface IEntityCheck {
|
||||
/**
|
||||
* Equips the check with a subject fitting the Entity. For instance, an
|
||||
* entity of File will receive an instance of OCP\Files\Node, or a comment
|
||||
* entity might get an IComment.
|
||||
*
|
||||
* The implementing check must be aware of the incoming type.
|
||||
*
|
||||
* If an unsupported subject is passed the implementation MAY throw an
|
||||
* \UnexpectedValueException.
|
||||
*
|
||||
* @param IEntity $entity
|
||||
* @param mixed $subject
|
||||
* @throws \UnexpectedValueException
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function setEntitySubject(IEntity $entity, $subject): void;
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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;
|
||||
|
||||
/**
|
||||
* Interface IEntityEvent
|
||||
*
|
||||
* represents an entitiy event that is dispatched via EventDispatcher
|
||||
*
|
||||
* @package OCP\WorkflowEngine
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
interface IEntityEvent {
|
||||
/**
|
||||
* returns a translated name to be presented in the web interface.
|
||||
*
|
||||
* Example: "created" (en), "kreita" (eo)
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getDisplayName(): string;
|
||||
|
||||
/**
|
||||
* returns the event name that is emitted by the EventDispatcher, e.g.:
|
||||
*
|
||||
* Example: "OCA\MyApp\Factory\Cats::postCreated"
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getEventName(): string;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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 IFileCheck
|
||||
*
|
||||
* @package OCP\WorkflowEngine
|
||||
* @since 18.0.0
|
||||
*/
|
||||
interface IFileCheck extends IEntityCheck {
|
||||
/**
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, string $path);
|
||||
|
||||
}
|
|
@ -23,9 +23,6 @@
|
|||
|
||||
namespace OCP\WorkflowEngine;
|
||||
|
||||
|
||||
use OCP\Files\Storage\IStorage;
|
||||
|
||||
/**
|
||||
* Interface IManager
|
||||
*
|
||||
|
@ -33,18 +30,40 @@ use OCP\Files\Storage\IStorage;
|
|||
* @since 9.1
|
||||
*/
|
||||
interface IManager {
|
||||
/**
|
||||
* @param IStorage $storage
|
||||
* @param string $path
|
||||
* @since 9.1
|
||||
*/
|
||||
public function setFileInfo(IStorage $storage, $path);
|
||||
|
||||
const SCOPE_ADMIN = 0;
|
||||
const SCOPE_USER = 1;
|
||||
|
||||
const EVENT_NAME_REG_OPERATION = 'OCP\WorkflowEngine::registerOperations';
|
||||
const EVENT_NAME_REG_ENTITY = 'OCP\WorkflowEngine::registerEntities';
|
||||
const EVENT_NAME_REG_CHECK = 'OCP\WorkflowEngine::registerChecks';
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param bool $returnFirstMatchingOperationOnly
|
||||
* @return array
|
||||
* @since 9.1
|
||||
* Listen to `\OCP\WorkflowEngine::EVENT_NAME_REG_ENTITY` at the
|
||||
* EventDispatcher for registering your entities.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getMatchingOperations($class, $returnFirstMatchingOperationOnly = true);
|
||||
public function registerEntity(IEntity $entity): void;
|
||||
|
||||
/**
|
||||
* Listen to `\OCP\WorkflowEngine::EVENT_NAME_REG_OPERATION` at the
|
||||
* EventDispatcher for registering your operators.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function registerOperation(IOperation $operator): void;
|
||||
|
||||
/**
|
||||
* Listen to `\OCP\WorkflowEngine::EVENT_NAME_REG_CHECK` at the
|
||||
* EventDispatcher for registering your operators.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function registerCheck(ICheck $check): void;
|
||||
|
||||
/**
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getRuleMatcher(): IRuleMatcher;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
|
||||
namespace OCP\WorkflowEngine;
|
||||
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
|
||||
/**
|
||||
* Interface IOperation
|
||||
*
|
||||
|
@ -31,11 +33,71 @@ namespace OCP\WorkflowEngine;
|
|||
*/
|
||||
interface IOperation {
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array[] $checks
|
||||
* @param string $operation
|
||||
* returns a translated name to be presented in the web interface
|
||||
*
|
||||
* Example: "Automated tagging" (en), "Aŭtomata etikedado" (eo)
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getDisplayName(): string;
|
||||
|
||||
/**
|
||||
* returns a translated, descriptive text to be presented in the web interface.
|
||||
*
|
||||
* It should be short and precise.
|
||||
*
|
||||
* Example: "Tag based automatic deletion of files after a given time." (en)
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getDescription(): string;
|
||||
|
||||
/**
|
||||
* returns the URL to the icon of the operator for display in the web interface.
|
||||
*
|
||||
* Usually, the implementation would utilize the `imagePath()` method of the
|
||||
* `\OCP\IURLGenerator` instance and simply return its result.
|
||||
*
|
||||
* Example implementation: return $this->urlGenerator->imagePath('myApp', 'cat.svg');
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getIcon(): string;
|
||||
|
||||
/**
|
||||
* returns whether the operation can be used in the requested scope.
|
||||
*
|
||||
* Scope IDs are defined as constants in OCP\WorkflowEngine\IManager. At
|
||||
* time of writing these are SCOPE_ADMIN and SCOPE_USER.
|
||||
*
|
||||
* For possibly unknown future scopes the recommended behaviour is: if
|
||||
* user scope is permitted, the default behaviour should return `true`,
|
||||
* otherwise `false`.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function isAvailableForScope(int $scope): bool;
|
||||
|
||||
/**
|
||||
* Validates whether a configured workflow rule is valid. If it is not,
|
||||
* an `\UnexpectedValueException` is supposed to be thrown.
|
||||
*
|
||||
* @throws \UnexpectedValueException
|
||||
* @since 9.1
|
||||
*/
|
||||
public function validateOperation($name, array $checks, $operation);
|
||||
public function validateOperation(string $name, array $checks, string $operation): void;
|
||||
|
||||
/**
|
||||
* Is being called by the workflow engine when an event was triggered that
|
||||
* is configured for this operation. An evaluation whether the event
|
||||
* qualifies for this operation to run has still to be done by the
|
||||
* implementor by calling the RuleMatchers getMatchingOperations method
|
||||
* and evaluating the results.
|
||||
*
|
||||
* If the implementor is an IComplexOperation, this method will not be
|
||||
* called automatically. It can be used or left as no-op by the implementor.
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function onEvent(string $eventName, GenericEvent $event, IRuleMatcher $ruleMatcher): void;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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;
|
||||
|
||||
/**
|
||||
* Class IRuleMatcher
|
||||
*
|
||||
* @package OCP\WorkflowEngine
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
interface IRuleMatcher extends IFileCheck {
|
||||
/**
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getMatchingOperations(string $class, bool $returnFirstMatchingOperationOnly = true): array;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.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;
|
||||
|
||||
/**
|
||||
* Interface ISpecificOperation
|
||||
*
|
||||
* This interface represents an operator that is designed to work with exactly
|
||||
* one entity type.
|
||||
*
|
||||
* In almost all of the cases it is not necessary to have this limitation,
|
||||
* because the action is not connected to the event. This mechanism suits
|
||||
* special cases.
|
||||
*
|
||||
* @package OCP\WorkflowEngine
|
||||
* @since 18.0.0
|
||||
*/
|
||||
interface ISpecificOperation extends IOperation {
|
||||
|
||||
/**
|
||||
* returns the id of the entity the operator is designed for
|
||||
*
|
||||
* Example: 'WorkflowEngine_Entity_File'
|
||||
*
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function getEntityId():string;
|
||||
}
|
|
@ -5,9 +5,9 @@
|
|||
"requires": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
|
||||
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
|
||||
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.0.0"
|
||||
|
@ -35,73 +35,12 @@
|
|||
"source-map": "^0.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
|
||||
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz",
|
||||
"integrity": "sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.6.0",
|
||||
"jsesc": "^2.5.1",
|
||||
"lodash": "^4.17.13",
|
||||
"source-map": "^0.5.0",
|
||||
"trim-right": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
|
||||
"integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz",
|
||||
"integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@babel/parser": "^7.6.0",
|
||||
"@babel/types": "^7.6.0"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz",
|
||||
"integrity": "sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.5.5",
|
||||
"@babel/generator": "^7.6.0",
|
||||
"@babel/helper-function-name": "^7.1.0",
|
||||
"@babel/helper-split-export-declaration": "^7.4.4",
|
||||
"@babel/parser": "^7.6.0",
|
||||
"@babel/types": "^7.6.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.13"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
|
||||
"integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
|
@ -132,17 +71,6 @@
|
|||
"trim-right": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
|
||||
"integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
|
@ -190,19 +118,6 @@
|
|||
"@babel/helper-function-name": "^7.1.0",
|
||||
"@babel/types": "^7.5.5",
|
||||
"lodash": "^4.17.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
|
||||
"integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-explode-assignable-expression": {
|
||||
|
@ -251,19 +166,6 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
|
||||
"integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
|
@ -287,19 +189,6 @@
|
|||
"@babel/template": "^7.4.4",
|
||||
"@babel/types": "^7.5.5",
|
||||
"lodash": "^4.17.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
|
||||
"integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-optimise-call-expression": {
|
||||
|
@ -349,19 +238,6 @@
|
|||
"@babel/helper-optimise-call-expression": "^7.0.0",
|
||||
"@babel/traverse": "^7.5.5",
|
||||
"@babel/types": "^7.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
|
||||
"integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-simple-access": {
|
||||
|
@ -404,92 +280,6 @@
|
|||
"@babel/template": "^7.6.0",
|
||||
"@babel/traverse": "^7.6.0",
|
||||
"@babel/types": "^7.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz",
|
||||
"integrity": "sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.6.0",
|
||||
"jsesc": "^2.5.1",
|
||||
"lodash": "^4.17.13",
|
||||
"source-map": "^0.5.0",
|
||||
"trim-right": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
|
||||
"integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz",
|
||||
"integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@babel/parser": "^7.6.0",
|
||||
"@babel/types": "^7.6.0"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz",
|
||||
"integrity": "sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.5.5",
|
||||
"@babel/generator": "^7.6.0",
|
||||
"@babel/helper-function-name": "^7.1.0",
|
||||
"@babel/helper-split-export-declaration": "^7.4.4",
|
||||
"@babel/parser": "^7.6.0",
|
||||
"@babel/types": "^7.6.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
|
||||
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
|
||||
"integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
|
@ -988,30 +778,25 @@
|
|||
"invariant": "^2.2.2",
|
||||
"js-levenshtein": "^1.1.3",
|
||||
"semver": "^5.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
|
||||
"integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz",
|
||||
"integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==",
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz",
|
||||
"integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@babel/parser": "^7.4.4",
|
||||
"@babel/types": "^7.4.4"
|
||||
"@babel/parser": "^7.6.0",
|
||||
"@babel/types": "^7.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/parser": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
|
||||
"integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
|
@ -1031,32 +816,12 @@
|
|||
"lodash": "^4.17.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
|
||||
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
|
||||
"integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
|
||||
"integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
|
@ -1069,13 +834,13 @@
|
|||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.0.tgz",
|
||||
"integrity": "sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ==",
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
|
||||
"integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
|
@ -3048,9 +2813,9 @@
|
|||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.252",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.252.tgz",
|
||||
"integrity": "sha512-NWJ5TztDnjExFISZHFwpoJjMbLUifsNBnx7u2JI0gCw6SbKyQYYWWtBHasO/jPtHym69F4EZuTpRNGN11MT/jg==",
|
||||
"version": "1.3.254",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.254.tgz",
|
||||
"integrity": "sha512-7I5/OkgR6JKy6RFLJeru0kc0RMmmMu1UnkHBKInFKRrg1/4EQKIqOaUqITSww/SZ1LqWwp1qc/LLoIGy449eYw==",
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
|
@ -3178,9 +2943,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"esutils": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
||||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true
|
||||
},
|
||||
"eventemitter3": {
|
||||
|
@ -3549,9 +3314,9 @@
|
|||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz",
|
||||
"integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==",
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.8.1.tgz",
|
||||
"integrity": "sha512-micCIbldHioIegeKs41DoH0KS3AXfFzgS30qVkM6z/XOE/GJgvmsoc839NUqa1B9udYe9dQxgv7KFwng6+p/dw==",
|
||||
"requires": {
|
||||
"debug": "^3.0.0"
|
||||
}
|
||||
|
@ -5651,9 +5416,9 @@
|
|||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
|
||||
"version": "0.0.8",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
},
|
||||
"mississippi": {
|
||||
"version": "3.0.0",
|
||||
|
@ -5719,6 +5484,14 @@
|
|||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
||||
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
|
||||
},
|
||||
"moment-timezone": {
|
||||
"version": "0.5.26",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.26.tgz",
|
||||
"integrity": "sha512-sFP4cgEKTCymBBKgoxZjYzlSovC20Y6J7y3nanDc5RoBIXKlZhoYwBoZGe3flwU6A372AcRwScH8KiwV6zjy1g==",
|
||||
"requires": {
|
||||
"moment": ">= 2.9.0"
|
||||
}
|
||||
},
|
||||
"move-concurrently": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||
|
@ -5782,6 +5555,21 @@
|
|||
"resolved": "https://registry.npmjs.org/nextcloud-password-confirmation/-/nextcloud-password-confirmation-0.4.2.tgz",
|
||||
"integrity": "sha512-DZXsfdk3iCsRWtd0lsYM1nqQ/oD9YlQ2WbC4qRZo20enUQLjJWZ8lYhKftXowmYL41t7spThnznJ7ihMG2/vUQ=="
|
||||
},
|
||||
"nextcloud-router": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/nextcloud-router/-/nextcloud-router-0.0.9.tgz",
|
||||
"integrity": "sha512-w0i4xqFwJJuXNWFf9AB9huCWW5XmwdJHSHa7oXlOLTAvP9WxwU3KCm/mcKy8Eb0cT0ElRPg72HLUxl7oyEWoBQ==",
|
||||
"requires": {
|
||||
"core-js": "^3.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
|
||||
"integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"nextcloud-vue": {
|
||||
"version": "0.12.3",
|
||||
"resolved": "https://registry.npmjs.org/nextcloud-vue/-/nextcloud-vue-0.12.3.tgz",
|
||||
|
@ -5928,9 +5716,9 @@
|
|||
}
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.29",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.29.tgz",
|
||||
"integrity": "sha512-R5bDhzh6I+tpi/9i2hrrvGJ3yKPYzlVOORDkXhnZuwi5D3q1I5w4vYy24PJXTcLk9Q0kws9TO77T75bcK8/ysQ==",
|
||||
"version": "1.1.30",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.30.tgz",
|
||||
"integrity": "sha512-BHcr1g6NeUH12IL+X3Flvs4IOnl1TL0JczUhEZjDE+FXXPQcVCNr8NEPb01zqGxzhTpdyJL5GXemaCW7aw6Khw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"semver": "^5.3.0"
|
||||
|
@ -6436,9 +6224,9 @@
|
|||
"integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA=="
|
||||
},
|
||||
"portfinder": {
|
||||
"version": "1.0.24",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.24.tgz",
|
||||
"integrity": "sha512-ekRl7zD2qxYndYflwiryJwMioBI7LI7rVXg3EnLK3sjkouT5eOuhS3gS255XxBksa30VG8UPZYZCdgfGOfkSUg==",
|
||||
"version": "1.0.23",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.23.tgz",
|
||||
"integrity": "sha512-B729mL/uLklxtxuiJKfQ84WPxNw5a7Yhx3geQZdcA4GjNjZSTSSMMWyoennMVnTWSmAR0lMdzWYN0JLnHrg1KQ==",
|
||||
"requires": {
|
||||
"async": "^1.5.2",
|
||||
"debug": "^2.2.0",
|
||||
|
@ -9276,7 +9064,7 @@
|
|||
},
|
||||
"wrap-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
|
|
@ -41,8 +41,10 @@
|
|||
"lodash": "^4.17.15",
|
||||
"marked": "^0.7.0",
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.26",
|
||||
"nextcloud-axios": "^0.2.1",
|
||||
"nextcloud-password-confirmation": "^0.4.2",
|
||||
"nextcloud-router": "0.0.9",
|
||||
"nextcloud-vue": "^0.12.3",
|
||||
"nextcloud-vue-collections": "^0.5.6",
|
||||
"query-string": "^5.1.1",
|
||||
|
|
|
@ -9,12 +9,15 @@
|
|||
namespace Test\Files\Node;
|
||||
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\Node\HookConnector;
|
||||
use OC\Files\Node\Root;
|
||||
use OC\Files\Storage\Temporary;
|
||||
use OC\Files\View;
|
||||
use OCP\Files\Node;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUserManager;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
use Test\TestCase;
|
||||
use Test\Traits\MountProviderTrait;
|
||||
use Test\Traits\UserTrait;
|
||||
|
@ -29,6 +32,9 @@ use Test\Traits\UserTrait;
|
|||
class HookConnectorTest extends TestCase {
|
||||
use UserTrait;
|
||||
use MountProviderTrait;
|
||||
/** @var \PHPUnit\Framework\MockObject\MockObject */
|
||||
/** @var EventDispatcherInterface */
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* @var View
|
||||
|
@ -60,6 +66,7 @@ class HookConnectorTest extends TestCase {
|
|||
$this->createMock(ILogger::class),
|
||||
$this->createMock(IUserManager::class)
|
||||
);
|
||||
$this->eventDispatcher = \OC::$server->getEventDispatcher();
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
|
@ -72,50 +79,50 @@ class HookConnectorTest extends TestCase {
|
|||
return [
|
||||
[function () {
|
||||
Filesystem::file_put_contents('test.txt', 'asd');
|
||||
}, 'preWrite'],
|
||||
}, 'preWrite', '\OCP\Files::preWrite'],
|
||||
[function () {
|
||||
Filesystem::file_put_contents('test.txt', 'asd');
|
||||
}, 'postWrite'],
|
||||
}, 'postWrite', '\OCP\Files::postWrite'],
|
||||
[function () {
|
||||
Filesystem::file_put_contents('test.txt', 'asd');
|
||||
}, 'preCreate'],
|
||||
}, 'preCreate', '\OCP\Files::preCreate'],
|
||||
[function () {
|
||||
Filesystem::file_put_contents('test.txt', 'asd');
|
||||
}, 'postCreate'],
|
||||
}, 'postCreate', '\OCP\Files::postCreate'],
|
||||
[function () {
|
||||
Filesystem::mkdir('test.txt');
|
||||
}, 'preCreate'],
|
||||
}, 'preCreate', '\OCP\Files::preCreate'],
|
||||
[function () {
|
||||
Filesystem::mkdir('test.txt');
|
||||
}, 'postCreate'],
|
||||
}, 'postCreate', '\OCP\Files::postCreate'],
|
||||
[function () {
|
||||
Filesystem::touch('test.txt');
|
||||
}, 'preTouch'],
|
||||
}, 'preTouch', '\OCP\Files::preTouch'],
|
||||
[function () {
|
||||
Filesystem::touch('test.txt');
|
||||
}, 'postTouch'],
|
||||
}, 'postTouch', '\OCP\Files::postTouch'],
|
||||
[function () {
|
||||
Filesystem::touch('test.txt');
|
||||
}, 'preCreate'],
|
||||
}, 'preCreate', '\OCP\Files::preCreate'],
|
||||
[function () {
|
||||
Filesystem::touch('test.txt');
|
||||
}, 'postCreate'],
|
||||
}, 'postCreate', '\OCP\Files::postCreate'],
|
||||
[function () {
|
||||
Filesystem::file_put_contents('test.txt', 'asd');
|
||||
Filesystem::unlink('test.txt');
|
||||
}, 'preDelete'],
|
||||
}, 'preDelete', '\OCP\Files::preDelete'],
|
||||
[function () {
|
||||
Filesystem::file_put_contents('test.txt', 'asd');
|
||||
Filesystem::unlink('test.txt');
|
||||
}, 'postDelete'],
|
||||
}, 'postDelete', '\OCP\Files::postDelete'],
|
||||
[function () {
|
||||
Filesystem::mkdir('test.txt');
|
||||
Filesystem::rmdir('test.txt');
|
||||
}, 'preDelete'],
|
||||
}, 'preDelete', '\OCP\Files::preDelete'],
|
||||
[function () {
|
||||
Filesystem::mkdir('test.txt');
|
||||
Filesystem::rmdir('test.txt');
|
||||
}, 'postDelete'],
|
||||
}, 'postDelete', '\OCP\Files::postDelete'],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -124,8 +131,8 @@ class HookConnectorTest extends TestCase {
|
|||
* @param string $expectedHook
|
||||
* @dataProvider viewToNodeProvider
|
||||
*/
|
||||
public function testViewToNode(callable $operation, $expectedHook) {
|
||||
$connector = new \OC\Files\Node\HookConnector($this->root, $this->view);
|
||||
public function testViewToNode(callable $operation, $expectedHook, $expectedEvent) {
|
||||
$connector = new HookConnector($this->root, $this->view, $this->eventDispatcher);
|
||||
$connector->viewToNode();
|
||||
$hookCalled = false;
|
||||
/** @var Node $hookNode */
|
||||
|
@ -136,10 +143,21 @@ class HookConnectorTest extends TestCase {
|
|||
$hookNode = $node;
|
||||
});
|
||||
|
||||
$dispatcherCalled = false;
|
||||
/** @var Node $dispatcherNode */
|
||||
$dispatcherNode = null;
|
||||
$this->eventDispatcher->addListener($expectedEvent, function (GenericEvent $event) use (&$dispatcherCalled, &$dispatcherNode) {
|
||||
$dispatcherCalled = true;
|
||||
$dispatcherNode = $event->getSubject();
|
||||
});
|
||||
|
||||
$operation();
|
||||
|
||||
$this->assertTrue($hookCalled);
|
||||
$this->assertEquals('/' . $this->userId . '/files/test.txt', $hookNode->getPath());
|
||||
|
||||
$this->assertTrue($dispatcherCalled);
|
||||
$this->assertEquals('/' . $this->userId . '/files/test.txt', $dispatcherNode->getPath());
|
||||
}
|
||||
|
||||
public function viewToNodeProviderCopyRename() {
|
||||
|
@ -147,19 +165,19 @@ class HookConnectorTest extends TestCase {
|
|||
[function () {
|
||||
Filesystem::file_put_contents('source', 'asd');
|
||||
Filesystem::rename('source', 'target');
|
||||
}, 'preRename'],
|
||||
}, 'preRename', '\OCP\Files::preRename'],
|
||||
[function () {
|
||||
Filesystem::file_put_contents('source', 'asd');
|
||||
Filesystem::rename('source', 'target');
|
||||
}, 'postRename'],
|
||||
}, 'postRename', '\OCP\Files::postRename'],
|
||||
[function () {
|
||||
Filesystem::file_put_contents('source', 'asd');
|
||||
Filesystem::copy('source', 'target');
|
||||
}, 'preCopy'],
|
||||
}, 'preCopy', '\OCP\Files::preCopy'],
|
||||
[function () {
|
||||
Filesystem::file_put_contents('source', 'asd');
|
||||
Filesystem::copy('source', 'target');
|
||||
}, 'postCopy'],
|
||||
}, 'postCopy', '\OCP\Files::postCopy'],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -168,8 +186,8 @@ class HookConnectorTest extends TestCase {
|
|||
* @param string $expectedHook
|
||||
* @dataProvider viewToNodeProviderCopyRename
|
||||
*/
|
||||
public function testViewToNodeCopyRename(callable $operation, $expectedHook) {
|
||||
$connector = new \OC\Files\Node\HookConnector($this->root, $this->view);
|
||||
public function testViewToNodeCopyRename(callable $operation, $expectedHook, $expectedEvent) {
|
||||
$connector = new HookConnector($this->root, $this->view, $this->eventDispatcher);
|
||||
$connector->viewToNode();
|
||||
$hookCalled = false;
|
||||
/** @var Node $hookSourceNode */
|
||||
|
@ -183,15 +201,29 @@ class HookConnectorTest extends TestCase {
|
|||
$hookTargetNode = $targetNode;
|
||||
});
|
||||
|
||||
$dispatcherCalled = false;
|
||||
/** @var Node $dispatcherSourceNode */
|
||||
$dispatcherSourceNode = null;
|
||||
/** @var Node $dispatcherTargetNode */
|
||||
$dispatcherTargetNode = null;
|
||||
$this->eventDispatcher->addListener($expectedEvent, function (GenericEvent $event) use (&$dispatcherSourceNode, &$dispatcherTargetNode, &$dispatcherCalled) {
|
||||
$dispatcherCalled = true;
|
||||
list($dispatcherSourceNode, $dispatcherTargetNode) = $event->getSubject();
|
||||
});
|
||||
|
||||
$operation();
|
||||
|
||||
$this->assertTrue($hookCalled);
|
||||
$this->assertEquals('/' . $this->userId . '/files/source', $hookSourceNode->getPath());
|
||||
$this->assertEquals('/' . $this->userId . '/files/target', $hookTargetNode->getPath());
|
||||
|
||||
$this->assertTrue($dispatcherCalled);
|
||||
$this->assertEquals('/' . $this->userId . '/files/source', $dispatcherSourceNode->getPath());
|
||||
$this->assertEquals('/' . $this->userId . '/files/target', $dispatcherTargetNode->getPath());
|
||||
}
|
||||
|
||||
public function testPostDeleteMeta() {
|
||||
$connector = new \OC\Files\Node\HookConnector($this->root, $this->view);
|
||||
$connector = new HookConnector($this->root, $this->view, $this->eventDispatcher);
|
||||
$connector->viewToNode();
|
||||
$hookCalled = false;
|
||||
/** @var Node $hookNode */
|
||||
|
@ -202,11 +234,22 @@ class HookConnectorTest extends TestCase {
|
|||
$hookNode = $node;
|
||||
});
|
||||
|
||||
$dispatcherCalled = false;
|
||||
/** @var Node $dispatcherNode */
|
||||
$dispatcherNode = null;
|
||||
$this->eventDispatcher->addListener('\OCP\Files::postDelete', function (GenericEvent $event) use (&$dispatcherCalled, &$dispatcherNode) {
|
||||
$dispatcherCalled = true;
|
||||
$dispatcherNode = $event->getSubject();
|
||||
});
|
||||
|
||||
Filesystem::file_put_contents('test.txt', 'asd');
|
||||
$info = Filesystem::getFileInfo('test.txt');
|
||||
Filesystem::unlink('test.txt');
|
||||
|
||||
$this->assertTrue($hookCalled);
|
||||
$this->assertEquals($hookNode->getId(), $info->getId());
|
||||
|
||||
$this->assertTrue($dispatcherCalled);
|
||||
$this->assertEquals($dispatcherNode->getId(), $info->getId());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue