@@ -46,3 +46,7 @@ | |||
margin-right: 5px; | |||
} | |||
.workflowengine .invalid-input { | |||
border-color: #aa0000; | |||
} | |||
@@ -296,7 +296,7 @@ | |||
_.each(OCA.WorkflowEngine.availablePlugins, function(plugin) { | |||
if (_.isFunction(plugin.render)) { | |||
plugin.render(valueElement, check['class'], check['value']); | |||
plugin.render(valueElement, check); | |||
} | |||
}); | |||
}, this); |
@@ -0,0 +1,72 @@ | |||
/** | |||
* @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 (upload)'), | |||
'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 = t('workflowengine', 'text/plain'); | |||
if (check['operator'] === 'matches' || check['operator'] === '!matches') { | |||
placeholder = t('workflowengine', '/^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); |
@@ -27,7 +27,7 @@ | |||
getCheck: function() { | |||
return { | |||
'class': 'OCA\\WorkflowEngine\\Check\\FileSize', | |||
'name': t('workflowengine', 'File size'), | |||
'name': t('workflowengine', 'File size (upload)'), | |||
'operators': [ | |||
{'operator': 'less', 'name': t('workflowengine', 'less')}, | |||
{'operator': '!greater', 'name': t('workflowengine', 'less or equals')}, | |||
@@ -36,14 +36,19 @@ | |||
] | |||
}; | |||
}, | |||
render: function(element, classname, value) { | |||
if (classname !== 'OCA\\WorkflowEngine\\Check\\FileSize') { | |||
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', t('workflowengine', '12 MB')) | |||
; | |||
.attr('placeholder', placeholder) | |||
.attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder})) | |||
.addClass('has-tooltip') | |||
.tooltip({ | |||
placement: 'bottom' | |||
}); | |||
} | |||
}; | |||
})(); |
@@ -41,8 +41,8 @@ | |||
] | |||
}; | |||
}, | |||
render: function(element, classname, value) { | |||
if (classname !== 'OCA\\WorkflowEngine\\Check\\FileSystemTags') { | |||
render: function(element, check) { | |||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileSystemTags') { | |||
return; | |||
} | |||
@@ -34,8 +34,8 @@ | |||
] | |||
}; | |||
}, | |||
render: function(element, classname, value) { | |||
if (classname !== 'OCA\\WorkflowEngine\\Check\\UserGroupMembership') { | |||
render: function(element, check) { | |||
if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\UserGroupMembership') { | |||
return; | |||
} | |||
@@ -55,6 +55,7 @@ class Application extends \OCP\AppFramework\App { | |||
'admin', | |||
// Check plugins | |||
'filemimetypeplugin', | |||
'filesizeplugin', | |||
'filesystemtagsplugin', | |||
'usergroupmembershipplugin', |
@@ -0,0 +1,110 @@ | |||
<?php | |||
/** | |||
* @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/>. | |||
* | |||
*/ | |||
namespace OCA\WorkflowEngine\Check; | |||
use OCP\Files\Storage\IStorage; | |||
use OCP\WorkflowEngine\ICheck; | |||
abstract class AbstractStringCheck implements ICheck { | |||
/** @var array[] Nested array: [Pattern => [ActualValue => Regex Result]] */ | |||
protected $matches; | |||
/** | |||
* @param IStorage $storage | |||
* @param string $path | |||
*/ | |||
public function setFileInfo(IStorage $storage, $path) { | |||
// Nothing changes here with a different path | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
abstract protected function getActualValue(); | |||
/** | |||
* @param string $operator | |||
* @param string $value | |||
* @return bool | |||
*/ | |||
public function executeCheck($operator, $value) { | |||
$actualValue = $this->getActualValue(); | |||
return $this->executeStringCheck($operator, $value, $actualValue); | |||
} | |||
/** | |||
* @param string $operator | |||
* @param string $checkValue | |||
* @param string $actualValue | |||
* @return bool | |||
*/ | |||
protected function executeStringCheck($operator, $checkValue, $actualValue) { | |||
if ($operator === 'is') { | |||
return $checkValue === $actualValue; | |||
} else if ($operator === '!is') { | |||
return $checkValue !== $actualValue; | |||
} else { | |||
$match = $this->match($checkValue, $actualValue); | |||
if ($operator === 'matches') { | |||
return $match === 1; | |||
} else { | |||
return $match === 0; | |||
} | |||
} | |||
} | |||
/** | |||
* @param string $operator | |||
* @param string $value | |||
* @throws \UnexpectedValueException | |||
*/ | |||
public function validateCheck($operator, $value) { | |||
if (!in_array($operator, ['is', '!is', 'matches', '!matches'])) { | |||
throw new \UnexpectedValueException('Invalid operator', 1); | |||
} | |||
if (in_array($operator, ['matches', '!matches']) && | |||
@preg_match($value, null) === false) { | |||
throw new \UnexpectedValueException('Invalid regex', 2); | |||
} | |||
} | |||
/** | |||
* @param string $pattern | |||
* @param string $subject | |||
* @return int|bool | |||
*/ | |||
protected function match($pattern, $subject) { | |||
$patternHash = md5($pattern); | |||
$subjectHash = md5($subject); | |||
if (isset($this->matches[$patternHash][$subjectHash])) { | |||
return $this->matches[$patternHash][$subjectHash]; | |||
} | |||
if (!isset($this->matches[$patternHash])) { | |||
$this->matches[$patternHash] = []; | |||
} | |||
$this->matches[$patternHash][$subjectHash] = preg_match($pattern, $subject); | |||
return $this->matches[$patternHash][$subjectHash]; | |||
} | |||
} |
@@ -0,0 +1,82 @@ | |||
<?php | |||
/** | |||
* @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/>. | |||
* | |||
*/ | |||
namespace OCA\WorkflowEngine\Check; | |||
use OCP\Files\IMimeTypeDetector; | |||
use OCP\IRequest; | |||
class FileMimeType extends AbstractStringCheck { | |||
/** @var string */ | |||
protected $mimeType; | |||
/** @var IRequest */ | |||
protected $request; | |||
/** @var IMimeTypeDetector */ | |||
protected $mimeTypeDetector; | |||
/** | |||
* @param IRequest $request | |||
* @param IMimeTypeDetector $mimeTypeDetector | |||
*/ | |||
public function __construct(IRequest $request, IMimeTypeDetector $mimeTypeDetector) { | |||
$this->request = $request; | |||
$this->mimeTypeDetector = $mimeTypeDetector; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
protected function getActualValue() { | |||
if ($this->mimeType !== null) { | |||
return $this->mimeType; | |||
} | |||
$this->mimeType = ''; | |||
if ($this->isWebDAVRequest()) { | |||
if ($this->request->getMethod() === 'PUT') { | |||
$path = $this->request->getPathInfo(); | |||
$this->mimeType = $this->mimeTypeDetector->detectPath($path); | |||
} | |||
} else if (in_array($this->request->getMethod(), ['POST', 'PUT'])) { | |||
$files = $this->request->getUploadedFile('files'); | |||
if (isset($files['type'][0])) { | |||
$this->mimeType = $files['type'][0]; | |||
} | |||
} | |||
return $this->mimeType; | |||
} | |||
/** | |||
* @return bool | |||
*/ | |||
protected function isWebDAVRequest() { | |||
return substr($this->request->getScriptName(), 0 - strlen('/remote.php')) === '/remote.php' && ( | |||
$this->request->getPathInfo() === '/webdav' || | |||
strpos($this->request->getPathInfo(), '/webdav/') === 0 || | |||
$this->request->getPathInfo() === '/dav/files' || | |||
strpos($this->request->getPathInfo(), '/dav/files/') === 0 | |||
); | |||
} | |||
} |
@@ -0,0 +1,149 @@ | |||
<?php | |||
/** | |||
* @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/>. | |||
* | |||
*/ | |||
namespace OCA\WorkflowEngine\Tests\Check; | |||
class AbstractStringCheckTest extends \Test\TestCase { | |||
public function dataExecuteStringCheck() { | |||
return [ | |||
['is', 'same', 'same', true], | |||
['is', 'different', 'not the same', false], | |||
['!is', 'same', 'same', false], | |||
['!is', 'different', 'not the same', true], | |||
['matches', '/match/', 'match', true], | |||
['matches', '/different/', 'not the same', false], | |||
['!matches', '/match/', 'match', false], | |||
['!matches', '/different/', 'not the same', true], | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataExecuteStringCheck | |||
* @param string $operation | |||
* @param string $checkValue | |||
* @param string $actualValue | |||
* @param bool $expected | |||
*/ | |||
public function testExecuteStringCheck($operation, $checkValue, $actualValue, $expected) { | |||
$check = $this->getMockBuilder('OCA\WorkflowEngine\Check\AbstractStringCheck') | |||
->setMethods([ | |||
'setPath', | |||
'executeCheck', | |||
'getActualValue', | |||
]) | |||
->getMock(); | |||
/** @var \OCA\WorkflowEngine\Check\AbstractStringCheck $check */ | |||
$this->assertEquals($expected, $this->invokePrivate($check, 'executeStringCheck', [$operation, $checkValue, $actualValue])); | |||
} | |||
public function dataValidateCheck() { | |||
return [ | |||
['is', '/Invalid(Regex/'], | |||
['!is', '/Invalid(Regex/'], | |||
['matches', '/Valid(Regex)/'], | |||
['!matches', '/Valid(Regex)/'], | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataValidateCheck | |||
* @param string $operator | |||
* @param string $value | |||
*/ | |||
public function testValidateCheck($operator, $value) { | |||
$check = $this->getMockBuilder('OCA\WorkflowEngine\Check\AbstractStringCheck') | |||
->setMethods([ | |||
'setPath', | |||
'executeCheck', | |||
'getActualValue', | |||
]) | |||
->getMock(); | |||
/** @var \OCA\WorkflowEngine\Check\AbstractStringCheck $check */ | |||
$check->validateCheck($operator, $value); | |||
} | |||
public function dataValidateCheckInvalid() { | |||
return [ | |||
['!!is', '', 1, 'Invalid operator'], | |||
['less', '', 1, 'Invalid operator'], | |||
['matches', '/Invalid(Regex/', 2, 'Invalid regex'], | |||
['!matches', '/Invalid(Regex/', 2, 'Invalid regex'], | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataValidateCheckInvalid | |||
* @param $operator | |||
* @param $value | |||
* @param $exceptionCode | |||
* @param $exceptionMessage | |||
*/ | |||
public function testValidateCheckInvalid($operator, $value, $exceptionCode, $exceptionMessage) { | |||
$check = $this->getMockBuilder('OCA\WorkflowEngine\Check\AbstractStringCheck') | |||
->setMethods([ | |||
'setPath', | |||
'executeCheck', | |||
'getActualValue', | |||
]) | |||
->getMock(); | |||
try { | |||
/** @var \OCA\WorkflowEngine\Check\AbstractStringCheck $check */ | |||
$check->validateCheck($operator, $value); | |||
} catch (\UnexpectedValueException $e) { | |||
$this->assertEquals($exceptionCode, $e->getCode()); | |||
$this->assertEquals($exceptionMessage, $e->getMessage()); | |||
} | |||
} | |||
public function dataMatch() { | |||
return [ | |||
['/valid/', 'valid', [], true], | |||
['/valid/', 'valid', [md5('/valid/') => [md5('valid') => false]], false], // Cache hit | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataMatch | |||
* @param string $pattern | |||
* @param string $subject | |||
* @param array[] $matches | |||
* @param bool $expected | |||
*/ | |||
public function testMatch($pattern, $subject, $matches, $expected) { | |||
$check = $this->getMockBuilder('OCA\WorkflowEngine\Check\AbstractStringCheck') | |||
->setMethods([ | |||
'setPath', | |||
'executeCheck', | |||
'getActualValue', | |||
]) | |||
->getMock(); | |||
$this->invokePrivate($check, 'matches', [$matches]); | |||
$this->assertEquals($expected, $this->invokePrivate($check, 'match', [$pattern, $subject])); | |||
} | |||
} |