diff --git a/lib/Exception/PlaceholderNotSubstituted.php b/lib/Exception/PlaceholderNotSubstituted.php new file mode 100644 index 0000000..9fd28c3 --- /dev/null +++ b/lib/Exception/PlaceholderNotSubstituted.php @@ -0,0 +1,43 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\WorkflowScript\Exception; + +use Exception; +use Throwable; + +class PlaceholderNotSubstituted extends Exception { + + /** @var string */ + private $placeholder; + + public function __construct(string $placeholder, Throwable $previous = null) { + parent::__construct('', 0, $previous); + $this->placeholder = $placeholder; + } + + public function getPlaceholder(): string { + return $this->placeholder; + } +} diff --git a/lib/Operation.php b/lib/Operation.php index aa4dafa..e1f9a40 100644 --- a/lib/Operation.php +++ b/lib/Operation.php @@ -25,8 +25,13 @@ use OC\Files\Filesystem; use OC\Files\View; +use OC\User\NoUserException; +use OCA\Files_Sharing\SharedStorage; +use OCA\GroupFolders\Mount\GroupFolderStorage; use OCA\WorkflowEngine\Entity\File; +use OCA\WorkflowScript\AppInfo\Application; use OCA\WorkflowScript\BackgroundJobs\Launcher; +use OCA\WorkflowScript\Exception\PlaceholderNotSubstituted; use OCP\BackgroundJob\IJobList; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\GenericEvent; @@ -35,6 +40,7 @@ use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; use OCP\IConfig; use OCP\IL10N; use OCP\IUser; @@ -43,6 +49,7 @@ use OCP\WorkflowEngine\IManager; use OCP\WorkflowEngine\IRuleMatcher; use OCP\WorkflowEngine\ISpecificOperation; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\GenericEvent as LegacyGenericEvent; class Operation implements ISpecificOperation { @@ -59,16 +66,30 @@ class Operation implements ISpecificOperation { private $rootFolder; /** @var IConfig */ private $config; + /** @var LoggerInterface */ + private $logger; - public function __construct(IManager $workflowEngineManager, IJobList $jobList, IL10N $l, IUserSession $session, IRootFolder $rootFolder, IConfig $config) { + public function __construct( + IManager $workflowEngineManager, + IJobList $jobList, + IL10N $l, + IUserSession $session, + IRootFolder $rootFolder, + IConfig $config, + LoggerInterface $logger + ) { $this->workflowEngineManager = $workflowEngineManager; $this->jobList = $jobList; $this->l = $l; $this->session = $session; $this->rootFolder = $rootFolder; $this->config = $config; + $this->logger = $logger; } + /** + * @throws PlaceholderNotSubstituted + */ protected function buildCommand(string $template, Node $node, string $event, array $extra = []) { $command = $template; @@ -78,17 +99,9 @@ protected function buildCommand(string $template, Node $node, string $event, arr if (strpos($command, '%n')) { // Nextcloud relative-path - $nodeID = -1; - try { - $nodeID = $node->getId(); - } catch (InvalidPathException $e) { - } catch (NotFoundException $e) { - } - - $base_path = $this->config->getSystemValue('datadirectory'); - - $path = Filesystem::getLocalFile(Filesystem::getPath($nodeID)); - $command = str_replace('%n', escapeshellarg(str_replace($base_path . '/', '', $path)), $command); + $ncRelPath = $this->replacePlaceholderN($node); + $command = str_replace('%n', escapeshellarg($ncRelPath), $command); + unset($ncRelPath); } if (strpos($command, '%f')) { @@ -146,6 +159,77 @@ protected function buildCommand(string $template, Node $node, string $event, arr return $command; } + /** + * @throws PlaceholderNotSubstituted + */ + protected function replacePlaceholderN(Node $node): string { + $owner = $node->getOwner(); + try { + $nodeID = $node->getId(); + $storage = $node->getStorage(); + } catch (NotFoundException | InvalidPathException $e) { + $context = [ + 'app' => 'workflow_script', + 'exception' => $e, + 'node' => $node, + ]; + $message = 'Could not get if of node {node}'; + if(isset($nodeID)) { + $message = 'Could not find storage for file ID {fid}, node: {node}'; + $context['fid'] = $nodeID; + } + + $this->logger->warning($message, $context); + throw new PlaceholderNotSubstituted('n', $e); + } + + if(isset($storage) && $storage->instanceOfStorage(GroupFolderStorage::class)) { + // group folders are always located within $DATADIR/__groupfolders/ + $absPath = $storage->getLocalFile($node->getPath()); + $pos = strpos($absPath, '/__groupfolders/'); + // if the string cannot be found, the fallback is absolute path + // it should never happen #famousLastWords + if($pos === false) { + $this->logger->warning( + 'Groupfolder path does not contain __groupfolders. File ID: {fid}, Node path: {path}, absolute path: {abspath}', + [ + 'app' => 'workflow_script', + 'fid' => $nodeID, + 'path' => $node->getPath(), + 'abspath' => $absPath, + ] + ); + } + $ncRelPath = substr($absPath, (int)$pos); + } elseif (isset($storage) && $storage->instanceOfStorage(SharedStorage::class)) { + try { + $folder = $this->rootFolder->getUserFolder($owner->getUID()); + } catch (NotPermittedException | NoUserException $e) { + throw new PlaceholderNotSubstituted('n', $e); + } + $nodes = $folder->getById($nodeID); + if(empty($nodes)) { + throw new PlaceholderNotSubstituted('n'); + } + $newNode = array_shift($nodes); + $ncRelPath = $newNode->getPath(); + } else { + $ncRelPath = $node->getPath(); + if (strpos($node->getPath(), $owner->getUID()) !== 0) { + $nodes = $this->rootFolder->getById($nodeID); + foreach ($nodes as $testNode) { + if (strpos($node->getPath(), $owner->getUID()) === 0) { + $ncRelPath = $testNode; + break; + } + } + } + } + $ncRelPath = ltrim($ncRelPath, '/'); + + return $ncRelPath; + } + /** * @throws \UnexpectedValueException * @since 9.1 @@ -221,7 +305,20 @@ public function onEvent(string $eventName, Event $event, IRuleMatcher $ruleMatch $matches = $ruleMatcher->getFlows(false); foreach ($matches as $match) { - $command = $this->buildCommand($match['operation'], $node, $eventName, $extra); + try { + $command = $this->buildCommand($match['operation'], $node, $eventName, $extra); + } catch (PlaceholderNotSubstituted $e) { + $this->logger->warning( + 'Could not substitute {placeholder} in {command} with node {node}', + [ + 'app' => 'workflow_script', + 'placeholder' => $e->getPlaceholder(), + 'command' => $match['operation'], + 'node' => $node, + 'exception' => $e, + ] + ); + } $args = ['command' => $command]; if (strpos($command, '%f')) { $args['path'] = $node->getPath();