├── License.md ├── README.md ├── composer.json ├── gpl-3.0.txt └── src ├── BundleConfiguration.php ├── Command ├── ClassMethodExecutorCommand.php ├── MaintenanceCommand.php └── MigrationGeneratorCommand.php ├── Controller ├── CallbackSettingsController.php ├── ConfigController.php ├── IndexController.php ├── MonitoringItemController.php └── RestController.php ├── DependencyInjection ├── Compiler │ └── ExecutorDefinitionPass.php ├── Configuration.php └── ElementsProcessManagerExtension.php ├── ElementsProcessManagerBundle.php ├── Enums ├── General.php └── Permissions.php ├── ExecutionTrait.php ├── ExecutionTraitClass.php ├── Executor ├── AbstractExecutor.php ├── Action │ ├── AbstractAction.php │ ├── Download.php │ ├── JsEvent.php │ └── OpenItem.php ├── Callback │ ├── AbstractCallback.php │ └── General.php ├── ClassMethod.php ├── Logger │ ├── AbstractLogger.php │ ├── Application.php │ ├── Console.php │ ├── EmailSummary.php │ └── File.php └── PimcoreCommand.php ├── Helper.php ├── Installer.php ├── Logger.php ├── Maintenance.php ├── Maintenance └── MaintenanceTask.php ├── Message ├── CheckCommandAliveMessage.php ├── ExecuteCommandMessage.php └── StopProcessMessage.php ├── MessageHandler ├── CheckCommandAliveHandler.php ├── ExecuteCommandHandler.php └── StopProcessHandler.php ├── MetaDataFile.php ├── Migrations ├── Version20210428000000.php ├── Version20210802000000.php ├── Version20230207000000.php ├── Version20230217000000.php ├── Version20230321092750.php ├── Version20230425000002.php └── Version20241211095632.php ├── Model ├── CallbackSetting.php ├── CallbackSetting │ ├── Dao.php │ ├── Listing.php │ └── Listing │ │ └── Dao.php ├── Configuration.php ├── Configuration │ ├── Dao.php │ ├── Listing.php │ └── Listing │ │ └── Dao.php ├── Dao │ └── AbstractDao.php ├── MonitoringItem.php └── MonitoringItem │ ├── Dao.php │ ├── Listing.php │ └── Listing │ └── Dao.php ├── Resources ├── config │ ├── doctrine_migrations.yml │ ├── maintenance.yml │ ├── pimcore │ │ ├── config.yaml │ │ └── routing.yml │ └── services.yml ├── public │ ├── css │ │ ├── admin.css │ │ └── logFileLogger.css │ ├── img │ │ └── sprite-open-item-action.png │ └── js │ │ ├── executor │ │ ├── action │ │ │ ├── abstractAction.js │ │ │ ├── download.js │ │ │ ├── jsEvent.js │ │ │ └── openItem.js │ │ ├── callback │ │ │ ├── abstractCallback.js │ │ │ ├── default.js │ │ │ ├── example.js │ │ │ └── executionNote.js │ │ ├── class │ │ │ ├── abstractExecutor.js │ │ │ ├── classMethod.js │ │ │ ├── command.js │ │ │ └── pimcoreCommand.js │ │ └── logger │ │ │ ├── abstractLogger.js │ │ │ ├── application.js │ │ │ ├── console.js │ │ │ ├── emailSummary.js │ │ │ └── file.js │ │ ├── helper │ │ └── form.js │ │ ├── jquery-3.7.1.min.js │ │ ├── logFileLogger.js │ │ ├── panel │ │ ├── callbackSetting.js │ │ ├── config.js │ │ ├── general.js │ │ └── monitoringItem.js │ │ ├── startup.js │ │ └── window │ │ ├── activeProcesses.js │ │ └── detailwindow.js ├── translations │ └── admin.en.csv └── views │ ├── MonitoringItem │ └── logFileLogger.html.twig │ └── reportEmail.html.twig ├── Service ├── CommandsValidator.php └── UploadManger.php └── SystemEventsListener.php /License.md: -------------------------------------------------------------------------------- 1 | # License 2 | Copyright (C) valantic CX Austria GmbH (valantic.at) 3 | 4 | This software is available under two different licenses: 5 | * GNU General Public License version 3 (GPLv3) as Pimcore Community Edition 6 | * Pimcore Enterprise License (PEL) 7 | 8 | The default Pimcore license, without a valid Pimcore Enterprise License agreement, is the Open-Source GPLv3 license. 9 | 10 | ## GNU General Public License version 3 (GPLv3) 11 | If you decide to choose the GPLv3 license, you must comply with the following terms: 12 | 13 | This program is free software: you can redistribute it and/or modify 14 | it under the terms of the GNU General Public License as published by 15 | the Free Software Foundation, either version 3 of the License, or 16 | (at your option) any later version. 17 | 18 | This program is distributed in the hope that it will be useful, 19 | but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | GNU General Public License for more details. 22 | 23 | You should have received a copy of the GNU General Public License 24 | along with this program. If not, see . 25 | 26 | ## Pimcore Enterprise License (PEL) 27 | Alternatively, commercial and supported versions of the program - also known as 28 | Enterprise Distributions - must be used in accordance with the terms and conditions 29 | contained in a separate written agreement between you and Pimcore GmbH. For more information about the Pimcore Enterprise License (PEL) please contact info@pimcore.com. 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pimcore Process Manager Bundle 2 | 3 | The Process Manager allows you to manage (define,execute...) arbitrary processes/commands in the Pimcore backend. 4 | You can display the execution progress of the script in the admin interface and the user can view the detailed log information. 5 | In addition you can define "actions" - e.g. download of a file after the process has finished. Furthermore callback actions 6 | are available (custom settings which the user can define for runtime execution) and the processes are monitored (you get an email if a process dies) 7 | 8 | ***Key features:*** 9 | - Execute custom script in background 10 | - Report the current execution state to the customer in the Pimcore admin 11 | - Define loggers (email, file, application logger)... per process 12 | - View detailed debug log information in the Pimcore admin 13 | - Scripts are monitored and you will receive an email if a job fails 14 | - Provide custom actions after a job has finished (e.g download a file) 15 | - Define custom Callback-Windows to provide the user the ability to define runtime execution options 16 | - Store/Manage CallbackSettings and reuse them at execution time 17 | - Define/execute the scripts as cronjobs 18 | - Support multiprocessing (execute multiple processes parallel) 19 | 20 | ## Topics 21 | * [Installation & updates](./doc/installationAndUpdates.md) 22 | * [Configuration](./doc/configuration.md) 23 | * [Getting started (basics)](./doc/gettingStarted.md) 24 | * [Commands validator](./doc/commandsValidator.md) 25 | * [Callbacks (Configuration windows)](./doc/callbacks.md) 26 | * [Actions](./doc/actions.md) 27 | * [Meta data files](./doc/metaDataFile.md) 28 | * [Rest API](./doc/restApi.md) 29 | * [How to use - Parallelization](./doc/usageParallelization.md) 30 | * [Migrations for configurations](./doc/configurationMigrations.md) 31 | * [Batch command execution](./doc/batchCommandExecution.md) 32 | * [Dedicated CLI server using Symfony messenger](./doc/symfonyMessenger.md) 33 | * [Migration from Pimcore 6 to Pimcore X](./doc/migration.md) 34 | 35 | ***First impressions:*** 36 | 37 | Job execution by user: 38 | ![process-manager-log](./doc/img/process-manager-active-processes.png) 39 | 40 | Job execution state: 41 | ![process-manager-log](./doc/img/process-manager-log.png) 42 | 43 | Job configuration: 44 | ![process-manager-job-management](./doc/img/process-manager-settings.png) 45 | 46 | Define Loggers per process / job: 47 | ![process-manager-job-management](./doc/img/loggers.png) 48 | 49 | Callback settings (user defined runtime settings): 50 | ![callback-settings](./doc/img/callback-settings.jpg) 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elements/process-manager-bundle", 3 | "type": "pimcore-bundle", 4 | "description": "The Process Manager allows you to manage (define,execute...) arbitrary processes/commands in the Pimcore backend.", 5 | "require": { 6 | "php": "~8.1.0 || ~8.2.0 || ~8.3.0", 7 | "pimcore/pimcore": "^11.0", 8 | "dragonmantank/cron-expression": "^v3.1.0" 9 | }, 10 | "autoload": { 11 | "psr-4": { 12 | "Elements\\Bundle\\ProcessManagerBundle\\": "src" 13 | } 14 | }, 15 | "extra": { 16 | "pimcore": { 17 | "bundles": [ 18 | "Elements\\Bundle\\ProcessManagerBundle\\ElementsProcessManagerBundle" 19 | ] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/BundleConfiguration.php: -------------------------------------------------------------------------------- 1 | $config 14 | */ 15 | public function __construct(protected array $config) 16 | { 17 | } 18 | 19 | public function getConfigurationMigrationsNamespace(): string 20 | { 21 | return $this->config['configurationMigrationsNamespace']; 22 | } 23 | 24 | public function getConfigurationMigrationsDirectory(): string 25 | { 26 | return $this->config['configurationMigrationsDirectory']; 27 | } 28 | 29 | /** 30 | * @return bool 31 | */ 32 | public function getDisableShortcutMenu() 33 | { 34 | return $this->config['disableShortcutMenu']; 35 | } 36 | 37 | public function getProcessTimeoutMinutes(): int 38 | { 39 | return $this->config['processTimeoutMinutes']; 40 | } 41 | 42 | public function getRefreshIntervalSeconds(): int 43 | { 44 | return $this->config['refreshIntervalSeconds'] ?? 3; 45 | } 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function getClassTypes() 51 | { 52 | $result = []; 53 | foreach (Enums\General::EXECUTOR_CLASS_TYPES as $type) { 54 | $result[$type] = $this->config[$type]; 55 | } 56 | 57 | return $result; 58 | } 59 | 60 | /** 61 | * @return array 62 | */ 63 | public function getAdditionalScriptExecutionUsers(): array 64 | { 65 | return (array)$this->config['additionalScriptExecutionUsers']; 66 | } 67 | 68 | /** 69 | * @return array 70 | */ 71 | public function getReportingEmailAddresses(): array 72 | { 73 | $addresses = (array)$this->config['reportingEmailAddresses']; 74 | 75 | return array_filter($addresses); 76 | } 77 | 78 | public function getArchiveThresholdLogs(): int 79 | { 80 | return (int)$this->config['archiveThresholdLogs']; 81 | } 82 | 83 | /** 84 | * @return array 85 | */ 86 | public function getRestApiUsers(): array 87 | { 88 | return (array)$this->config['restApiUsers']; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Command/ClassMethodExecutorCommand.php: -------------------------------------------------------------------------------- 1 | addOption('monitoring-item-id', null, InputOption::VALUE_REQUIRED, 'Contains the monitoring item if executed via the Pimcore backend'); 26 | } 27 | 28 | protected function execute(InputInterface $input, OutputInterface $output): int 29 | { 30 | static::initProcessManager($input->getOption('monitoring-item-id')); 31 | self::checkExecutingUser((array)ElementsProcessManagerBundle::getConfiguration()->getAdditionalScriptExecutionUsers()); 32 | 33 | $configValues = static::getMonitoringItem()->getConfigValues(); 34 | $class = new $configValues['executorClass'](); 35 | $class->{$configValues['executorMethod']}(); 36 | 37 | return Command::SUCCESS; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Command/MaintenanceCommand.php: -------------------------------------------------------------------------------- 1 | addOption('monitoring-item-id', null, InputOption::VALUE_REQUIRED, 'Contains the monitoring item if executed via the Pimcore backend'); 33 | } 34 | 35 | protected function execute(InputInterface $input, OutputInterface $output): int 36 | { 37 | $options = ElementsProcessManagerBundle::getMaintenanceOptions(); 38 | $monitoringItem = static::initProcessManager($input->getOption('monitoring-item-id'), $options); 39 | static::doUniqueExecutionCheck(null, ['command' => static::getCommand($options)]); 40 | 41 | self::checkExecutingUser((array)ElementsProcessManagerBundle::getConfiguration()->getAdditionalScriptExecutionUsers()); 42 | 43 | $maintenance = new Maintenance($this->templatingEngine); 44 | $maintenance->execute(); 45 | 46 | return Command::SUCCESS; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Command/MigrationGeneratorCommand.php: -------------------------------------------------------------------------------- 1 | setName('process-manager:migrations:generate') 26 | ->setDescription('Generates a migration for a specific configuration.') 27 | ->addOption( 28 | 'configuration', 29 | null, 30 | InputOption::VALUE_REQUIRED, 31 | 'Configuration id for which the migration should be generated.' 32 | ); 33 | } 34 | 35 | protected string $template = '; 40 | 41 | use Doctrine\DBAL\Schema\Schema; 42 | use Doctrine\Migrations\AbstractMigration; 43 | use Elements\Bundle\ProcessManagerBundle\ElementsProcessManagerBundle; 44 | use Elements\Bundle\ProcessManagerBundle\Model\Configuration; 45 | 46 | final class extends AbstractMigration 47 | { 48 | public function getDescription(): string 49 | { 50 | return "Migration for ProcessManager configuration \'\'"; 51 | } 52 | 53 | /** 54 | * @var array 55 | */ 56 | protected array $configurationData = ; 57 | 58 | 59 | public function up(Schema $schema): void 60 | { 61 | $db = \Pimcore\Db::get(); 62 | 63 | $configurationData = \Pimcore\Db\Helper::quoteDataIdentifiers($db,$this->configurationData); 64 | if(Configuration::getById($this->configurationData[\'id\'])) { 65 | $db->update( 66 | ElementsProcessManagerBundle::TABLE_NAME_CONFIGURATION, 67 | $configurationData, 68 | [\'id\' => $this->configurationData[\'id\']] 69 | ); 70 | }else{ 71 | $db->insert( 72 | ElementsProcessManagerBundle::TABLE_NAME_CONFIGURATION, 73 | $configurationData 74 | ); 75 | } 76 | } 77 | 78 | public function down(Schema $schema): void 79 | { 80 | //not implemented 81 | } 82 | }'; 83 | 84 | protected function execute(InputInterface $input, OutputInterface $output): int 85 | { 86 | $helper = $this->getHelper('question'); 87 | $options = ['all' => 'All configurations']; 88 | $configList = new Configuration\Listing(); 89 | foreach ($configList->load() as $config) { 90 | $options[$config->getId()] = $config->getName(); 91 | } 92 | 93 | $configurationIds = []; 94 | if ($input->getOption('configuration')) { 95 | $configurationIds = explode_and_trim(',', $input->getOption('configuration')); 96 | } else { 97 | $question = new ChoiceQuestion( 98 | 'For which configuration would you like to generate a migration (defaults to "all")', 99 | $options, 100 | 'all' 101 | ); 102 | $question->setMultiselect(true); 103 | /** 104 | * @var QuestionHelper $helper 105 | */ 106 | $configurationIds = $helper->ask($input, $output, $question); 107 | if (in_array('all', $configurationIds)) { 108 | $configurationIds = array_keys($options); 109 | $configurationIds = array_diff($configurationIds, ['all']); 110 | } 111 | } 112 | 113 | $config = ElementsProcessManagerBundle::getConfiguration(); 114 | $configMigrationsDirectory = $config->getConfigurationMigrationsDirectory(); 115 | 116 | $filesystem = new Filesystem(); 117 | $filesystem->mkdir($configMigrationsDirectory); 118 | 119 | $db = \Pimcore\Db::get(); 120 | 121 | foreach ($configurationIds as $configurationId) { 122 | $data = $db->fetchAssociative( 123 | 'SELECT * FROM `' . ElementsProcessManagerBundle::TABLE_NAME_CONFIGURATION . '` WHERE `id` = :id ', 124 | ['id' => $configurationId] 125 | ); 126 | if (!$data) { 127 | $output->writeln('Configuration with id "' . $configurationId . '" not found.'); 128 | 129 | continue; 130 | } 131 | 132 | $template = $this->template; 133 | $template = str_replace('', $data['id'], $template); 134 | $template = str_replace('', var_export($data, true), $template); 135 | $template = str_replace( 136 | '', 137 | $config->getConfigurationMigrationsNamespace(), 138 | $template 139 | ); 140 | $file = $this->getVersionFilePath( 141 | 'VersionProcessManager' . ucfirst(\Pimcore\File::getValidFilename($data['id'])), 142 | $configMigrationsDirectory, 143 | 1 144 | ); 145 | 146 | $versionName = str_replace('.php', '', basename($file)); 147 | $template = str_replace('', $versionName, $template); 148 | 149 | $file = $configMigrationsDirectory . '/' . $versionName . '.php'; 150 | file_put_contents($file, $template); 151 | $output->writeln('Migration file created: ' . $file . ''); 152 | } 153 | 154 | return self::SUCCESS; 155 | } 156 | 157 | protected function getVersionFilePath( 158 | string $versionName, 159 | string $configMigrationsDirectory, 160 | int $versionNumber 161 | ): string { 162 | $file = $configMigrationsDirectory . '/' . $versionName . '_' . $versionNumber . '.php'; 163 | while (file_exists($file)) { 164 | $versionNumber++; 165 | $file = $this->getVersionFilePath($versionName, $configMigrationsDirectory, $versionNumber); 166 | } 167 | 168 | return $file; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Controller/CallbackSettingsController.php: -------------------------------------------------------------------------------- 1 | get('values'), true, 512, JSON_THROW_ON_ERROR); 30 | $settings = json_decode((string)$request->get('settings'), true, 512, JSON_THROW_ON_ERROR); 31 | if ($request->get('id')) { 32 | $setting = CallbackSetting::getById($request->get('id')); 33 | } else { 34 | $setting = new CallbackSetting(); 35 | } 36 | 37 | $setting = $setting->setName($values['name']) 38 | ->setDescription($values['description']) 39 | ->setType($request->get('type')) 40 | ->setSettings($request->get('settings'))->save(); 41 | 42 | return $this->jsonResponse(['success' => true, 'id' => $setting->getId()]); 43 | } catch (\Exception $e) { 44 | return $this->jsonResponse(['success' => false, 'message' => $e->getMessage()]); 45 | } 46 | } 47 | 48 | #[Route(path: '/delete')] 49 | public function deleteAction(Request $request): JsonResponse 50 | { 51 | try { 52 | $setting = CallbackSetting::getById($request->get('id')); 53 | if (!$setting) { 54 | throw new NotFoundException( 55 | sprintf('Callback setting id %d', $request->get('id')) 56 | ); 57 | } 58 | $setting->delete(); 59 | 60 | return $this->jsonResponse(['success' => true]); 61 | } catch (\Exception $e) { 62 | return $this->jsonResponse(['success' => false, 'message' => $e->getMessage()]); 63 | } 64 | } 65 | 66 | #[Route(path: '/copy')] 67 | public function copyAction(Request $request): JsonResponse 68 | { 69 | try { 70 | $setting = CallbackSetting::getById($request->get('id')); 71 | if ($setting) { 72 | $setting->setId(null)->setName('Copy - ' . $setting->getName())->save(); 73 | 74 | return $this->jsonResponse(['success' => true]); 75 | } else { 76 | throw new \Exception("CallbackSetting whith the id '" . $request->get('id') . "' doesn't exist."); 77 | } 78 | } catch (\Exception $e) { 79 | return $this->jsonResponse(['success' => false, 'message' => $e->getMessage()]); 80 | } 81 | } 82 | 83 | #[Route(path: '/list')] 84 | public function listAction(Request $request): JsonResponse 85 | { 86 | $list = new CallbackSetting\Listing(); 87 | $list->setOrder('DESC'); 88 | $list->setOrderKey('id'); 89 | $list->setLimit($request->get('limit', 25)); 90 | $list->setOffset($request->get('start', 0)); 91 | if ($filterCondition = QueryParams::getFilterCondition((string)$request->get('filter'))) { 92 | $list->setCondition($filterCondition); 93 | } 94 | 95 | if ($id = $request->get('id')) { 96 | $list->setCondition(' `id` = ?', [$id]); 97 | } elseif ($type = $request->get('type')) { 98 | $list->setCondition(' `type` = ?', [$type]); 99 | } 100 | 101 | $data = []; 102 | foreach ($list->load() as $item) { 103 | $tmp = $item->getObjectVars(); 104 | $tmp['extJsSettings'] = json_decode((string)$tmp['settings'], true, 512, JSON_THROW_ON_ERROR); 105 | $data[] = $tmp; 106 | } 107 | 108 | return $this->jsonResponse(['total' => $list->getTotalCount(), 'success' => true, 'data' => $data]); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Controller/IndexController.php: -------------------------------------------------------------------------------- 1 | checkPermission(Enums\Permissions::VIEW); 37 | } catch (AccessDeniedHttpException $e) { 38 | return $this->jsonResponse([]); 39 | } 40 | 41 | $bundleConfig = ElementsProcessManagerBundle::getConfiguration(); 42 | $data = $bundleConfig->getClassTypes(); 43 | 44 | $commands = (array)$commandsValidator->getValidCommands(); 45 | foreach ($commands as $key => $command) { 46 | $tmp = ['description' => $command->getDescription(), 'options' => $command->getDefinition()->getOptions()]; 47 | $commands[$key] = $tmp; 48 | } 49 | 50 | $data['pimcoreCommands'] = $commands; 51 | 52 | $data['roles'] = []; 53 | 54 | $list = new \Pimcore\Model\User\Role\Listing(); 55 | $list->setOrder('ASC')->setOrderKey('name'); 56 | foreach ($list->load() as $role) { 57 | $data['roles'][] = [ 58 | 'id' => $role->getId(), 59 | 'name' => $role->getName(), 60 | ]; 61 | } 62 | 63 | $data['permissions'] = []; 64 | $list = new \Pimcore\Model\User\Permission\Definition\Listing(); 65 | $list->setOrder('ASC')->setOrderKey('key'); 66 | foreach ($list->load() as $permission) { 67 | $data['permissions'][] = [ 68 | 'key' => $permission->getKey(), 69 | 'name' => $translator->trans($permission->getKey(), [], 'admin'). ' (' . $permission->getKey().')', 70 | 'category' => $permission->getCategory(), 71 | ]; 72 | } 73 | 74 | usort($data['permissions'], fn ($a, $b): int => strnatcasecmp($a['name'], $b['name'])); 75 | 76 | $shortCutMenu = []; 77 | 78 | if ($bundleConfig->getDisableShortcutMenu() == false) { 79 | $list = new Configuration\Listing(); 80 | $list->setUser($this->getPimcoreUser()); 81 | $list->setOrderKey('name'); 82 | foreach ($list->load() as $config) { 83 | $group = $config->getGroup() ?: 'default'; 84 | $shortCutMenu[$group][] = [ 85 | 'id' => $config->getId(), 86 | 'name' => $config->getName(), 87 | 'group' => $config->getGroup(), 88 | ]; 89 | } 90 | $data['shortCutMenu'] = $shortCutMenu ?: false; 91 | } 92 | 93 | $data['refreshIntervalSeconds'] = $bundleConfig->getRefreshIntervalSeconds(); 94 | 95 | if ($data['shortCutMenu'] ?? null) { 96 | ksort($data['shortCutMenu'], SORT_LOCALE_STRING); 97 | } 98 | 99 | return $this->jsonResponse($data); 100 | } 101 | 102 | /** 103 | * @throws \JsonException 104 | */ 105 | #[Route(path: '/download')] 106 | public function downloadAction(Request $request): ?Response 107 | { 108 | $monitoringItem = MonitoringItem::getById($request->get('id')); 109 | if (!$monitoringItem) { 110 | throw $this->createNotFoundException('MonitoringItem Not Found'); 111 | } 112 | $actions = $monitoringItem->getActions(); 113 | foreach ($actions as $action) { 114 | if ($action['accessKey'] == $request->get('accessKey')) { 115 | $className = $action['class']; 116 | /** 117 | * @var AbstractAction $class 118 | */ 119 | $class = new $className(); 120 | 121 | return $class->execute($monitoringItem, $action); 122 | } 123 | } 124 | 125 | return null; 126 | } 127 | 128 | #[Route(path: '/property-list')] 129 | public function propertyListAction(Request $request): JsonResponse 130 | { 131 | $result = []; 132 | $fieldName = $request->get('fieldName'); 133 | 134 | if ($fieldName == 'myProperties') { 135 | $result = []; 136 | for ($i = 1; $i < 50; $i++) { 137 | $result[] = ['id' => $i, 'name' => 'Display text - '.$fieldName.' - '.$i]; 138 | } 139 | } 140 | 141 | return $this->jsonResponse(['success' => true, 'data' => $result]); 142 | } 143 | 144 | /** 145 | * 146 | * @return JsonResponse 147 | * 148 | */ 149 | #[Route(path: '/get-classes')] 150 | public function getClassesAction(): JsonResponse 151 | { 152 | $result = []; 153 | $list = new ClassDefinition\Listing(); 154 | $list->setOrderKey('name')->setOrder('ASC'); 155 | foreach ($list as $c) { 156 | $result[] = ['id' => $c->getId(), 'name' => $c->getName()]; 157 | } 158 | 159 | return new JsonResponse(['data' => $result]); 160 | } 161 | 162 | /** 163 | * 164 | * @return JsonResponse 165 | * 166 | */ 167 | #[Route(path: '/get-grid-configs')] 168 | public function getGridConfigsAction(): JsonResponse 169 | { 170 | $result = []; 171 | $list = new GridConfig\Listing(); 172 | $list->setOrderKey('name'); 173 | $list->setCondition('ownerId = ? OR shareGlobally =1', [$this->getPimcoreUser()->getId()]); 174 | $config = $list->load(); 175 | foreach ($list as $c) { 176 | $result[] = ['id' => $c->getId(), 'name' => $c->getName()]; 177 | } 178 | 179 | return new JsonResponse(['data' => $result]); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Controller/RestController.php: -------------------------------------------------------------------------------- 1 | get('username')); 26 | if (!$user instanceof User) { 27 | return $this->json(['success' => false, 'message' => 'User not found']); 28 | } 29 | 30 | $config = \Elements\Bundle\ProcessManagerBundle\ElementsProcessManagerBundle::getConfiguration(); 31 | $validApiUser = false; 32 | 33 | foreach ($config->getRestApiUsers() as $entry) { 34 | if ($entry['username'] == $user->getName()) { 35 | if ($request->get('apiKey') == $entry['apiKey']) { 36 | $validApiUser = true; 37 | } else { 38 | return $this->json(['success' => false, 'message' => 'No valid api key for user']); 39 | } 40 | } 41 | } 42 | if (!$validApiUser) { 43 | return $this->json(['success' => false, 'message' => 'The user is not a valid api user']); 44 | } 45 | if (!$user->getPermission(Enums\Permissions::EXECUTE) || !$user->getPermission(Enums\Permissions::VIEW)) { 46 | return $this->json(['success' => false, 'message' => 'Missing permissions for user']); 47 | } 48 | 49 | return $user; 50 | } 51 | 52 | #[Route(path: '/execute')] 53 | public function executeAction(Request $request): JsonResponse 54 | { 55 | $user = $this->getApiUser($request); 56 | if (!$user instanceof User) { 57 | return $user; 58 | } 59 | 60 | if (!$request->get('id') && !$request->get('name')) { 61 | return $this->json(['success' => false, 'message' => 'Please provide a "name" or "id" parameter/value.']); 62 | } 63 | 64 | $list = new Configuration\Listing(); 65 | $list->setUser($user); 66 | if ($id = $request->get('id')) { 67 | $list->setCondition('id = ?', [$id]); 68 | } elseif ($name = $request->get('name')) { 69 | $list->setCondition('name = ?', [$name]); 70 | } 71 | $config = $list->current(); 72 | if (!$config) { 73 | return $this->json(['success' => false, 'message' => "Couldn't find a process to execute."]); 74 | } 75 | 76 | $callbackSettings = []; 77 | 78 | if ($val = $request->get('callbackSettings')) { 79 | $callbackSettings = json_decode((string) $val, true, 512, JSON_THROW_ON_ERROR); 80 | if (!is_array($callbackSettings)) { 81 | $xml = @simplexml_load_string((string) $val); 82 | if ($xml !== false) { 83 | $callbackSettings = json_decode(json_encode($xml, JSON_THROW_ON_ERROR), true, 512, JSON_THROW_ON_ERROR); 84 | } 85 | } 86 | 87 | if (!$callbackSettings) { 88 | return $this->json(['success' => false, 'message' => "Couldn't decode the callbackSettigs. Please make sure that you passed a valid JSON or XML."]); 89 | } 90 | } 91 | 92 | $result = Helper::executeJob($config->getId(), $callbackSettings, $user->getId()); 93 | unset($result['executedCommand']); 94 | 95 | return $this->json($result); 96 | } 97 | 98 | #[Route(path: '/monitoring-item-state')] 99 | public function monitoringItemStateAction(Request $request): JsonResponse 100 | { 101 | $user = $this->getApiUser($request); 102 | if ($user instanceof User == false) { 103 | return $user; 104 | } 105 | 106 | $list = new MonitoringItem\Listing(); 107 | $list->setUser($user); 108 | 109 | $list->setCondition(' id = ?', [$request->get('id')]); 110 | 111 | $monitoringItem = $list->current(); 112 | if (!$monitoringItem) { 113 | return $this->json(['success' => false, 'message' => 'The monitoring Item was not found.']); 114 | } 115 | $monitoringItem->getLogger()->notice('Checked by rest webservice User ID: ' . $user->getId()); 116 | 117 | return $this->json(['success' => true, 'data' => $monitoringItem->getForWebserviceExport()]); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/DependencyInjection/Compiler/ExecutorDefinitionPass.php: -------------------------------------------------------------------------------- 1 | getParameter('elements_process_manager'); 25 | 26 | foreach (Enums\General::EXECUTOR_CLASS_TYPES as $category) { 27 | $config[$category] = []; 28 | $taggedServices = $container->findTaggedServiceIds("elements.processManager.$category"); 29 | if ($taggedServices !== []) { 30 | foreach (array_keys($taggedServices) as $id) { 31 | $object = $container->get($id); 32 | 33 | $tmp = [ 34 | 'name' => $object->getName(), 35 | 'extJsClass' => $object->getExtJsClass(), 36 | 'class' => $object::class, 37 | 'config' => $object->getConfig(), 38 | ]; 39 | if ($object instanceof Executor\Callback\AbstractCallback) { 40 | $tmp['jsFile'] = $object->getJsFile(); 41 | } 42 | $config[$category][$object->getName()] = $tmp; 43 | } 44 | } 45 | } 46 | $container->setParameter('elements_process_manager', $config); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | getRootNode(); 29 | /** 30 | * @phpstan-ignore-next-line 31 | */ 32 | $rootNode 33 | ->children() 34 | ->integerNode('archiveThresholdLogs') 35 | ->defaultValue(7) 36 | ->min(0) 37 | ->info('Defines how many days log entries are kept') 38 | ->end() 39 | ->integerNode('processTimeoutMinutes') 40 | ->defaultValue(15) 41 | ->min(0) 42 | ->info('If the MonitoringItem has not been save within X minutes it will be considered as hanging process') 43 | ->end() 44 | ->integerNode('refreshIntervalSeconds') 45 | ->defaultValue(3) 46 | ->min(1) 47 | ->info('Refresh interal of process list in seconds') 48 | ->end() 49 | ->booleanNode('disableShortcutMenu') 50 | ->defaultValue(false) 51 | ->info('Disable the shortcut menu on the left side in the Pimcore admin') 52 | ->end() 53 | ->arrayNode('reportingEmailAddresses') 54 | ->defaultValue($debugEmailAddresses) 55 | ->scalarPrototype()->end() 56 | ->info('Defines email addresses to which errors should be reported') 57 | ->end() 58 | 59 | ->arrayNode('additionalScriptExecutionUsers') 60 | ->scalarPrototype()->end() 61 | ->info('Defines additional system users which are allowed to execute the php scripts') 62 | ->end() 63 | ->scalarNode('configurationMigrationsDirectory') 64 | ->defaultValue('%kernel.project_dir%/src/Migrations') 65 | ->info('Defines the directory where the bin/console process-manager:migrations:generate creates the migration files') 66 | ->end() 67 | ->scalarNode('configurationMigrationsNamespace') 68 | ->defaultValue("App\Migrations") 69 | ->info('Namespace for the configuration migrations') 70 | ->end() 71 | ->arrayNode('restApiUsers') 72 | ->arrayPrototype()->children() 73 | ->scalarNode('username')->isRequired()->end() 74 | ->scalarNode('apiKey')->isRequired()->end() 75 | 76 | ->end() 77 | ; 78 | 79 | return $treeBuilder; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/DependencyInjection/ElementsProcessManagerExtension.php: -------------------------------------------------------------------------------- 1 | hasExtension('doctrine_migrations')) { 21 | $loader = new YamlFileLoader( 22 | $container, 23 | new FileLocator(__DIR__ . '/../Resources/config') 24 | ); 25 | 26 | $loader->load('doctrine_migrations.yml'); 27 | } 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | * 33 | * @param array> $mergedConfig 34 | * @param ContainerBuilder $container 35 | * 36 | * @return void 37 | * 38 | * @throws \Exception 39 | */ 40 | public function loadInternal(array $mergedConfig, ContainerBuilder $container): void 41 | { 42 | $container->setParameter('elements_process_manager', $mergedConfig); 43 | 44 | $loader = new YamlFileLoader( 45 | $container, 46 | new FileLocator(__DIR__ . '/../Resources/config') 47 | ); 48 | 49 | $loader->load('services.yml'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Enums/General.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | protected array $values = []; 26 | 27 | /** 28 | * @var array 29 | */ 30 | protected array $loggers = []; 31 | 32 | /** 33 | * @var array 34 | */ 35 | protected array $executorConfig = []; 36 | 37 | /** 38 | * @var array 39 | */ 40 | protected array $actions = []; 41 | 42 | protected bool $isShellCommand = false; 43 | 44 | public function __construct() 45 | { 46 | } 47 | 48 | public function getIsShellCommand(): bool 49 | { 50 | return $this->isShellCommand; 51 | } 52 | 53 | public function setIsShellCommand(bool $isShellCommand): self 54 | { 55 | $this->isShellCommand = $isShellCommand; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * @return string 62 | */ 63 | public function getName(): string 64 | { 65 | if ($this->name === '' || $this->name === '0') { 66 | $array = explode('\\', static::class); 67 | $this->name = lcfirst(array_pop($array)); 68 | } 69 | 70 | return $this->name; 71 | } 72 | 73 | /** 74 | * @param string $name 75 | * 76 | * @return $this 77 | */ 78 | public function setName(string $name) 79 | { 80 | $this->name = $name; 81 | 82 | return $this; 83 | } 84 | 85 | public function getConfig(): ?Configuration 86 | { 87 | return $this->config; 88 | } 89 | 90 | public function setConfig(Configuration $config): self 91 | { 92 | $this->config = $config; 93 | 94 | return $this; 95 | } 96 | 97 | public function getExtJsClass(): string 98 | { 99 | return $this->extJsClass; 100 | } 101 | 102 | public function setExtJsClass(string $extJsClass): self 103 | { 104 | $this->extJsClass = $extJsClass; 105 | 106 | return $this; 107 | } 108 | 109 | /** 110 | * @return array 111 | */ 112 | public function getValues(): array 113 | { 114 | return $this->values; 115 | } 116 | 117 | /** 118 | * @param array $values 119 | * 120 | * @return $this 121 | */ 122 | public function setValues(array $values) 123 | { 124 | $this->values = $values; 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * @return array 131 | */ 132 | public function getExtJsSettings(): array 133 | { 134 | $data = []; 135 | $executorConfig = [ 136 | 'extJsClass' => $this->getExtJsClass(), 137 | 'name' => $this->getName(), 138 | 'class' => $this->getConfig()->getExecutorClass(), 139 | ]; 140 | $data['executorConfig'] = $executorConfig; 141 | 142 | $data['values'] = $this->getValues(); 143 | $data['values']['id'] = $this->getConfig()->getId(); 144 | 145 | $data['loggers'] = $this->getLoggers(); 146 | $data['actions'] = $this->getActions(); 147 | 148 | foreach ((array)$data['actions'] as $i => $actionData) { 149 | $className = $actionData['class']; 150 | $x = new $className(); 151 | $data['actions'][$i]['extJsClass'] = $x->getExtJsClass(); 152 | $data['actions'][$i]['config'] = $x->getConfig(); 153 | } 154 | 155 | foreach ((array)$data['loggers'] as $i => $loggerData) { 156 | $className = $loggerData['class']; 157 | $x = new $className(); 158 | $data['loggers'][$i]['extJsClass'] = $x->getExtJsClass(); 159 | $data['loggers'][$i]['config'] = $x->getConfig(); 160 | } 161 | 162 | return $data; 163 | } 164 | 165 | /** 166 | * @return array 167 | */ 168 | public function getActions(): array 169 | { 170 | return $this->actions; 171 | } 172 | 173 | /** 174 | * @param array $actions 175 | * 176 | * @return $this 177 | */ 178 | public function setActions(array $actions): self 179 | { 180 | $this->actions = $actions; 181 | 182 | return $this; 183 | } 184 | 185 | public function getShellCommand(MonitoringItem $monitoringItem): string 186 | { 187 | return Console::getPhpCli() . ' ' . realpath(PIMCORE_PROJECT_ROOT . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'console') . ' process-manager:execute-shell-cmd --monitoring-item-id=' . $monitoringItem->getId(); 188 | } 189 | 190 | /** 191 | * returns the command which should be executed 192 | * 193 | * the CallbackSettings are only passed at execution time 194 | * 195 | * @param string[] $callbackSettings 196 | * @param null | MonitoringItem $monitoringItem 197 | * 198 | * @return mixed 199 | */ 200 | abstract public function getCommand($callbackSettings = [], $monitoringItem = null); 201 | 202 | public function jsonSerialize(): mixed 203 | { 204 | return ['class' => static::class, ...get_object_vars($this)]; 205 | } 206 | 207 | /** 208 | * @return array 209 | */ 210 | public function getLoggers() 211 | { 212 | return $this->loggers; 213 | } 214 | 215 | /** 216 | * @param array $loggers 217 | * 218 | * @return $this 219 | */ 220 | public function setLoggers(array $loggers) 221 | { 222 | $this->loggers = $loggers; 223 | 224 | return $this; 225 | } 226 | 227 | public function getStorageValue(): string 228 | { 229 | $actions = (array)$this->getActions(); 230 | foreach ($actions as $i => $data) { 231 | if (is_object($data) && method_exists($data, 'getStorageData')) { 232 | $actions[$i] = $data->getStorageData(); 233 | } 234 | } 235 | $data = [ 236 | 'values' => (array)$this->getValues(), 237 | 'actions' => $actions, 238 | 'loggers' => (array)$this->getLoggers(), 239 | ]; 240 | 241 | return json_encode($data, JSON_THROW_ON_ERROR); 242 | } 243 | 244 | /** 245 | * @param array $values 246 | * 247 | * @return $this 248 | */ 249 | protected function setData(array $values) 250 | { 251 | foreach ($values as $key => $value) { 252 | $setter = 'set' . ucfirst((string)$key); 253 | if (method_exists($this, $setter)) { 254 | $this->$setter($value); 255 | } 256 | } 257 | 258 | return $this; 259 | } 260 | 261 | public function setDataFromResource(Configuration $configuration): Configuration 262 | { 263 | $settings = $configuration->getExecutorSettings(); 264 | if (!empty($settings)) { 265 | $this->setData(json_decode($settings, true, 512, JSON_THROW_ON_ERROR)); 266 | } 267 | $this->setConfig($configuration); 268 | 269 | return $configuration; 270 | } 271 | 272 | /** 273 | * Validate the configuration settings and throw exception if something is wrong 274 | */ 275 | public function validateConfiguration(Configuration $configuration): void 276 | { 277 | 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/Executor/Action/AbstractAction.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public array $executeAtStates = ['finished']; 24 | 25 | /** 26 | * @var array 27 | */ 28 | protected array $config = []; 29 | 30 | /** 31 | * @param $key string 32 | * 33 | * @return string 34 | */ 35 | protected function trans(string $key): string 36 | { 37 | $translator = \Pimcore::getKernel()->getContainer()->get('translator'); 38 | 39 | return $translator->trans($key, [], 'admin'); 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | public function getExtJsClass() 46 | { 47 | return $this->extJsClass; 48 | } 49 | 50 | /** 51 | * @param string $extJsClass 52 | * 53 | * @return $this 54 | */ 55 | public function setExtJsClass(string $extJsClass) 56 | { 57 | $this->extJsClass = $extJsClass; 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * @return string 64 | */ 65 | public function getName() 66 | { 67 | return $this->name; 68 | } 69 | 70 | /** 71 | * @param string $name 72 | * 73 | * @return $this 74 | */ 75 | public function setName(string $name) 76 | { 77 | $this->name = $name; 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * @return array 84 | */ 85 | public function getExecuteAtStates(): array 86 | { 87 | return $this->executeAtStates; 88 | } 89 | 90 | /** 91 | * @param array $executeAtStates 92 | * 93 | * @return $this 94 | */ 95 | public function setExecuteAtStates(array $executeAtStates) 96 | { 97 | $this->executeAtStates = $executeAtStates; 98 | 99 | return $this; 100 | } 101 | 102 | /** 103 | * @param array $data 104 | * 105 | * @return void 106 | */ 107 | public function setValues(array $data): void 108 | { 109 | $data = $this->prepareDataForSetValues($data); 110 | foreach ($data as $key => $value) { 111 | $setter = 'set' . ucfirst($key); 112 | if (method_exists($this, $setter)) { 113 | $this->$setter($value); 114 | } 115 | } 116 | } 117 | 118 | /** 119 | * @param array $data 120 | * 121 | * @return array 122 | */ 123 | protected function prepareDataForSetValues(array $data): array 124 | { 125 | return $data; 126 | } 127 | 128 | /** 129 | * @return array 130 | */ 131 | public function getConfig(): array 132 | { 133 | return $this->config; 134 | } 135 | 136 | /** 137 | * @param array $config 138 | * 139 | * @return $this 140 | */ 141 | public function setConfig(array $config) 142 | { 143 | $this->config = $config; 144 | 145 | return $this; 146 | } 147 | 148 | /** 149 | * @param $monitoringItem MonitoringItem 150 | * @param array $actionData 151 | * 152 | * @return string 153 | */ 154 | abstract public function getGridActionHtml(MonitoringItem $monitoringItem, array $actionData): string; 155 | 156 | /** 157 | * Performs the action 158 | * 159 | * @param $monitoringItem MonitoringItem 160 | * @param array $actionData 161 | * 162 | * @return mixed 163 | */ 164 | abstract public function execute(MonitoringItem $monitoringItem, array $actionData); 165 | 166 | /** 167 | * @param $monitoringItem MonitoringItem 168 | * @param array $actionData 169 | */ 170 | public function preMonitoringItemDeletion(MonitoringItem $monitoringItem, array $actionData): void 171 | { 172 | } 173 | 174 | /** 175 | * returns data which can be used in action classes 176 | * 177 | * @param MonitoringItem $monitoringItem 178 | * @param array $actionData 179 | * 180 | * @return array 181 | */ 182 | public function toJson(MonitoringItem $monitoringItem, array $actionData): array 183 | { 184 | return $this->getObjectVars(); 185 | } 186 | 187 | /** 188 | * returns an array for storage in the database 189 | * 190 | * @return array 191 | */ 192 | abstract public function getStorageData(): array; 193 | } 194 | -------------------------------------------------------------------------------- /src/Executor/Action/Download.php: -------------------------------------------------------------------------------- 1 | deleteWithMonitoringItem; 48 | } 49 | 50 | /** 51 | * @param bool $deleteWithMonitoringItem 52 | * 53 | * @return $this 54 | */ 55 | public function setDeleteWithMonitoringItem($deleteWithMonitoringItem) 56 | { 57 | $this->deleteWithMonitoringItem = $deleteWithMonitoringItem; 58 | 59 | return $this; 60 | } 61 | 62 | public function getAccessKey(): string 63 | { 64 | return $this->accessKey; 65 | } 66 | 67 | /** 68 | * @param string $accessKey 69 | * 70 | * @return $this 71 | */ 72 | public function setAccessKey(string $accessKey) 73 | { 74 | $this->accessKey = $accessKey; 75 | 76 | return $this; 77 | } 78 | 79 | public function getLabel(): string 80 | { 81 | return $this->label; 82 | } 83 | 84 | /** 85 | * @param string $label 86 | * 87 | * @return $this 88 | */ 89 | public function setLabel($label) 90 | { 91 | $this->label = $label; 92 | 93 | return $this; 94 | } 95 | 96 | public function getFilePath(): string 97 | { 98 | return $this->filePath; 99 | } 100 | 101 | /** 102 | * @param string $filePath 103 | * 104 | * @return $this 105 | */ 106 | public function setFilePath($filePath) 107 | { 108 | $this->filePath = $filePath; 109 | 110 | return $this; 111 | } 112 | 113 | public function isAbsoluteFilePath(): bool 114 | { 115 | return $this->isAbsoluteFilePath; 116 | } 117 | 118 | public function setIsAbsoluteFilePath(bool $isAbsoluteFilePath): Download 119 | { 120 | $this->isAbsoluteFilePath = $isAbsoluteFilePath; 121 | 122 | return $this; 123 | } 124 | 125 | /** 126 | * @param array $actionData 127 | * 128 | * @return string 129 | */ 130 | protected function buildFilePath(array $actionData): string 131 | { 132 | $filePath = $actionData['filepath']; 133 | $isAbsoluteFilePath = $actionData['isAbsoluteFilePath'] ?? $this->isAbsoluteFilePath(); 134 | 135 | return $isAbsoluteFilePath ? $filePath : PIMCORE_PROJECT_ROOT.$filePath; 136 | } 137 | 138 | /** 139 | * @param $monitoringItem MonitoringItem 140 | * @param array $actionData 141 | * 142 | * @return bool 143 | */ 144 | protected function downloadFileExists(MonitoringItem $monitoringItem, array $actionData): bool 145 | { 146 | $file = $actionData['filepath'] ? $this->buildFilePath($actionData) : $monitoringItem->getLogFile(); 147 | 148 | return is_readable($file); 149 | } 150 | 151 | /** 152 | * @param $monitoringItem MonitoringItem 153 | * @param array $actionData 154 | * 155 | * @return string 156 | */ 157 | public function getGridActionHtml(MonitoringItem $monitoringItem, array $actionData): string 158 | { 159 | if (in_array($monitoringItem->getStatus(), $actionData['executeAtStates'])) { 160 | 161 | $downloadFileExists = $this->downloadFileExists($monitoringItem, $actionData); 162 | if ($downloadFileExists) { 163 | 164 | return '  '; 170 | } else { 171 | return $this->trans('plugin_pm_download_file_doesnt_exist'); 172 | } 173 | } 174 | 175 | return ''; 176 | } 177 | 178 | /** Performs the action 179 | * 180 | * @param MonitoringItem $monitoringItem 181 | * @param array $actionData 182 | * 183 | * @return BinaryFileResponse 184 | * 185 | * @throws \Exception 186 | */ 187 | public function execute(MonitoringItem $monitoringItem, array $actionData) 188 | { 189 | $file = $this->buildFilePath($actionData); 190 | if (is_readable($file)) { 191 | $response = new BinaryFileResponse($file); 192 | $response->headers->set('Content-Type', finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file), true); 193 | $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, basename((string) $file)); 194 | 195 | return $response; 196 | } else { 197 | throw new \Exception('Download file "'.$file.'" not present'); 198 | } 199 | } 200 | 201 | /** 202 | * @param MonitoringItem $monitoringItem 203 | * @param array $actionData 204 | * 205 | * @return void 206 | */ 207 | public function preMonitoringItemDeletion(MonitoringItem $monitoringItem, array $actionData): void 208 | { 209 | if ($actionData['deleteWithMonitoringItem'] == true || $actionData['deleteWithMonitoringItem'] == 'on') { 210 | $file = $this->buildFilePath($actionData); 211 | if (is_readable($file) && is_file($file)) { 212 | unlink($file); 213 | } 214 | } 215 | } 216 | 217 | /** 218 | * @param MonitoringItem $monitoringItem 219 | * @param array $actionData 220 | * 221 | * @return array 222 | */ 223 | public function toJson(MonitoringItem $monitoringItem, array $actionData): array 224 | { 225 | $data = parent::toJson($monitoringItem, $actionData); 226 | if (in_array($monitoringItem->getStatus(), $actionData['executeAtStates'])) { 227 | $data['fileExists'] = $this->downloadFileExists($monitoringItem, $actionData); 228 | } else { 229 | $data['fileExists'] = false; 230 | } 231 | 232 | return $data; 233 | } 234 | 235 | /** 236 | * @return array 237 | */ 238 | public function getStorageData(): array 239 | { 240 | return [ 241 | 'accessKey' => $this->getAccessKey(), 242 | 'label' => $this->getLabel(), 243 | 'filepath' => $this->getFilePath(), 244 | 'deleteWithMonitoringItem' => $this->getDeleteWithMonitoringItem(), 245 | 'isAbsoluteFilePath' => $this->isAbsoluteFilePath(), 246 | 'executeAtStates' => $this->getExecuteAtStates(), 247 | 'class' => self::class, 248 | ]; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/Executor/Action/JsEvent.php: -------------------------------------------------------------------------------- 1 | label; 29 | } 30 | 31 | /** 32 | * @param string $label 33 | * 34 | * @return $this 35 | */ 36 | public function setLabel(string $label) 37 | { 38 | $this->label = $label; 39 | 40 | return $this; 41 | } 42 | 43 | public function getEventName(): string 44 | { 45 | return $this->eventName; 46 | } 47 | 48 | /** 49 | * @param string $eventName 50 | * 51 | * @return $this 52 | */ 53 | public function setEventName(string $eventName) 54 | { 55 | $this->eventName = $eventName; 56 | 57 | return $this; 58 | } 59 | 60 | public function getEventData(): string 61 | { 62 | return $this->eventData; 63 | } 64 | 65 | /** 66 | * @param string $eventData 67 | * 68 | * @return $this 69 | */ 70 | public function setEventData(string $eventData) 71 | { 72 | $this->eventData = $eventData; 73 | 74 | return $this; 75 | } 76 | 77 | public function getIcon(): string 78 | { 79 | return $this->icon; 80 | } 81 | 82 | /** 83 | * @param string $icon 84 | * 85 | * @return $this 86 | */ 87 | public function setIcon(string $icon) 88 | { 89 | $this->icon = $icon; 90 | 91 | return $this; 92 | } 93 | 94 | /** 95 | * @param MonitoringItem $monitoringItem 96 | * @param array $actionData 97 | * 98 | * @return string 99 | * 100 | * @throws \JsonException 101 | */ 102 | public function getGridActionHtml(MonitoringItem $monitoringItem, array $actionData): string 103 | { 104 | $img = ''; 105 | 106 | return '' . $img . ''; 112 | 113 | } 114 | 115 | /** 116 | * @param $monitoringItem MonitoringItem 117 | * @param array $actionData 118 | * 119 | * @return void 120 | */ 121 | public function execute(MonitoringItem $monitoringItem, array $actionData): void 122 | { 123 | } 124 | 125 | /** 126 | * @return array 127 | */ 128 | public function getStorageData(): array 129 | { 130 | 131 | return [ 132 | 'label' => $this->getLabel(), 133 | 'icon' => $this->getIcon(), 134 | 'eventName' => $this->getEventName(), 135 | 'eventData' => $this->getEventData(), 136 | 'class' => self::class, 137 | ]; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Executor/Action/OpenItem.php: -------------------------------------------------------------------------------- 1 | label; 27 | } 28 | 29 | /** 30 | * @param string $label 31 | * 32 | * @return $this 33 | */ 34 | public function setLabel(string $label) 35 | { 36 | $this->label = $label; 37 | 38 | return $this; 39 | } 40 | 41 | public function getType(): string 42 | { 43 | return $this->type; 44 | } 45 | 46 | /** 47 | * @param string $type 48 | * 49 | * @return $this 50 | */ 51 | public function setType(string $type) 52 | { 53 | $this->type = $type; 54 | 55 | return $this; 56 | } 57 | 58 | public function getItemId(): ?int 59 | { 60 | return $this->itemId; 61 | } 62 | 63 | /** 64 | * @param array $data 65 | * 66 | * @return array 67 | */ 68 | protected function prepareDataForSetValues(array $data): array 69 | { 70 | if (isset($data['itemId']) && $data['itemId'] === '') { 71 | $data['itemId'] = null; 72 | } 73 | 74 | return parent::prepareDataForSetValues($data); 75 | } 76 | 77 | public function setItemId(?int $itemId): self 78 | { 79 | $this->itemId = $itemId; 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * @param $monitoringItem MonitoringItem 86 | * @param array $actionData 87 | * 88 | * @return object|null 89 | */ 90 | protected function getItem(MonitoringItem $monitoringItem, array $actionData): ?object 91 | { 92 | return \Pimcore\Model\Element\Service::getElementById($actionData['type'], $actionData['itemId']); 93 | } 94 | 95 | /** 96 | * @param $type string 97 | * 98 | * @return string 99 | */ 100 | protected function getIcon(string $type): string 101 | { 102 | $icons = [ 103 | 'document' => '/bundles/pimcoreadmin/img/flat-white-icons/page.svg', 104 | 'object' => '/bundles/pimcoreadmin/img/flat-white-icons/object.svg', 105 | 'asset' => '/bundles/pimcoreadmin/img/flat-white-icons/camera.svg', 106 | ]; 107 | 108 | return $icons[$type]; 109 | } 110 | 111 | /** 112 | * @param $monitoringItem MonitoringItem 113 | * @param array $actionData 114 | * 115 | * @return string 116 | */ 117 | public function getGridActionHtml(MonitoringItem $monitoringItem, array $actionData): string 118 | { 119 | if (in_array($monitoringItem->getStatus(), $actionData['executeAtStates'])) { 120 | 121 | $item = $this->getItem($monitoringItem, $actionData); 122 | if ($item) { 123 | $icon = $this->getIcon($actionData['type']); 124 | $type = $item->getType(); 125 | $method = 'pimcore.helpers.open'.ucfirst((string) $actionData['type']); 126 | $cssClass = 'process_manager_icon_action_open '.$actionData['type'].' '; 127 | 128 | return '  '; 137 | } else { 138 | return $this->trans('plugin_pm_item_doesnt_exist'); 139 | } 140 | } 141 | 142 | return ''; 143 | } 144 | 145 | /** 146 | * @param MonitoringItem $monitoringItem 147 | * @param $actionData array 148 | * 149 | * @return array 150 | */ 151 | public function toJson(MonitoringItem $monitoringItem, array $actionData): array 152 | { 153 | $data = parent::toJson($monitoringItem, $actionData); 154 | $data['item_exists'] = false; 155 | $data['item_type'] = null; 156 | 157 | if (in_array($monitoringItem->getStatus(), $actionData['executeAtStates'])) { 158 | $item = $this->getItem($monitoringItem, $actionData); 159 | if ($item) { 160 | $data['item_exists'] = true; 161 | $data['item_type'] = $item->getType(); 162 | } else { 163 | $data['item_exists'] = false; 164 | } 165 | } 166 | 167 | return $data; 168 | } 169 | 170 | /** 171 | * @param $monitoringItem MonitoringItem 172 | * @param array $actionData 173 | * 174 | * @return void 175 | */ 176 | public function execute(MonitoringItem $monitoringItem, array $actionData): void 177 | { 178 | } 179 | 180 | /** 181 | * @return array 182 | */ 183 | public function getStorageData(): array 184 | { 185 | return [ 186 | 'label' => $this->getLabel(), 187 | 'type' => $this->getType(), 188 | 'itemId' => $this->getItemId(), 189 | 'executeAtStates' => $this->getExecuteAtStates(), 190 | 'class' => self::class, 191 | ]; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/Executor/Callback/AbstractCallback.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | protected array $config = []; 22 | 23 | /** 24 | * AbstractCallback constructor. 25 | * 26 | * @param string $name 27 | * @param string $extJsClass 28 | * @param string $jsFile 29 | * @param array $config 30 | */ 31 | public function __construct(string $name, string $extJsClass, string $jsFile = '', array $config = []) 32 | { 33 | $this->setName($name); 34 | $this->setExtJsClass($extJsClass); 35 | $this->setJsFile($jsFile); 36 | 37 | foreach ($config as $key => $value) { 38 | $setter = 'set'.ucfirst($key); 39 | if (method_exists($this, $setter)) { 40 | $this->$setter($value); 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getExtJsClass() 49 | { 50 | return $this->extJsClass; 51 | } 52 | 53 | /** 54 | * @param string $extJsClass 55 | */ 56 | public function setExtJsClass(string $extJsClass): void 57 | { 58 | $this->extJsClass = $extJsClass; 59 | } 60 | 61 | /** 62 | * @return string 63 | */ 64 | public function getName() 65 | { 66 | return $this->name; 67 | } 68 | 69 | /** 70 | * @param string $name 71 | */ 72 | public function setName(string $name): void 73 | { 74 | $this->name = $name; 75 | } 76 | 77 | /** 78 | * @return array 79 | */ 80 | public function getConfig(): array 81 | { 82 | return $this->config; 83 | } 84 | 85 | /** 86 | * @param array $config 87 | * 88 | */ 89 | public function setConfig(array $config): self 90 | { 91 | $this->config = $config; 92 | 93 | return $this; 94 | } 95 | 96 | public function getJsFile(): string 97 | { 98 | return $this->jsFile; 99 | } 100 | 101 | public function setJsFile(string $jsFile): self 102 | { 103 | $this->jsFile = $jsFile; 104 | 105 | return $this; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Executor/Callback/General.php: -------------------------------------------------------------------------------- 1 | getId(); 31 | } 32 | 33 | return $command; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Executor/Logger/AbstractLogger.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | protected array $config = []; 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getExtJsClass() 31 | { 32 | return $this->extJsClass; 33 | } 34 | 35 | /** 36 | * @param string $extJsClass 37 | * 38 | * @return $this 39 | */ 40 | public function setExtJsClass(string $extJsClass) 41 | { 42 | $this->extJsClass = $extJsClass; 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function getName() 51 | { 52 | return $this->name; 53 | } 54 | 55 | /** 56 | * @param string $name 57 | * 58 | * @return $this 59 | */ 60 | public function setName(string $name) 61 | { 62 | $this->name = $name; 63 | 64 | return $this; 65 | } 66 | 67 | /** 68 | * @return array 69 | */ 70 | public function getConfig(): array 71 | { 72 | return $this->config; 73 | } 74 | 75 | /** 76 | * @param array $config 77 | * 78 | * @return $this 79 | */ 80 | public function setConfig(array $config) 81 | { 82 | $this->config = $config; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * @param $monitoringItem MonitoringItem 89 | * @param array $actionData 90 | * 91 | * @return string 92 | */ 93 | abstract public function getGridLoggerHtml(MonitoringItem $monitoringItem, array $actionData): string; 94 | 95 | /** 96 | * @param array $config 97 | * @param MonitoringItem $monitoringItem 98 | * 99 | */ 100 | abstract public function createStreamHandler(array $config, MonitoringItem $monitoringItem): StreamHandler | ApplicationLoggerDb | null; 101 | } 102 | -------------------------------------------------------------------------------- /src/Executor/Logger/Application.php: -------------------------------------------------------------------------------- 1 | $actionData 24 | * 25 | * @return string 26 | */ 27 | public function getGridLoggerHtml(MonitoringItem $monitoringItem, array $actionData): string 28 | { 29 | 30 | return 'Application Logger'; 35 | } 36 | 37 | /** 38 | * @param array $config 39 | * 40 | */ 41 | public function createStreamHandler(array $config, MonitoringItem $monitoringItem): ApplicationLoggerDb 42 | { 43 | if (!isset($this->streamHandler)) { 44 | if (!$config['logLevel']) { 45 | $config['logLevel'] = 'DEBUG'; 46 | } 47 | $logLevel = constant('\Psr\Log\LogLevel::'.$config['logLevel']); 48 | 49 | $this->streamHandler = new ApplicationLoggerDb(\Pimcore\Db::get(), $logLevel); 50 | } 51 | 52 | return $this->streamHandler; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Executor/Logger/Console.php: -------------------------------------------------------------------------------- 1 | $actionData 24 | * 25 | * @return string 26 | */ 27 | public function getGridLoggerHtml(MonitoringItem $monitoringItem, array $actionData): string 28 | { 29 | return ''; 30 | } 31 | 32 | /** 33 | * @param array $config 34 | * @param MonitoringItem $monitoringItem 35 | */ 36 | public function createStreamHandler(array $config, MonitoringItem $monitoringItem): ?StreamHandler 37 | { 38 | if (!$this->streamHandler && php_sapi_name() === 'cli') { 39 | if (empty($config['logLevel'])) { 40 | $config['logLevel'] = 'DEBUG'; 41 | } 42 | 43 | $logLevel = constant('\Psr\Log\LogLevel::'.$config['logLevel']); 44 | $this->streamHandler = new StreamHandler('php://stdout', $logLevel); 45 | 46 | if ($config['simpleLogFormat'] ?? false) { 47 | $this->streamHandler->setFormatter(new \Monolog\Formatter\LineFormatter(self::LOG_FORMAT_SIMPLE)); 48 | } 49 | } 50 | 51 | return $this->streamHandler; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Executor/Logger/EmailSummary.php: -------------------------------------------------------------------------------- 1 | $actionData 25 | * 26 | * @return string 27 | */ 28 | public function getGridLoggerHtml(MonitoringItem $monitoringItem, array $actionData): string 29 | { 30 | return ''; 31 | } 32 | 33 | /** 34 | * @param array $config 35 | * @param MonitoringItem $monitoringItem 36 | * 37 | * @return StreamHandler|null 38 | * 39 | * @throws \JsonException 40 | */ 41 | public function createStreamHandler(array $config, MonitoringItem $monitoringItem): ?StreamHandler 42 | { 43 | if (!$this->streamHandler) { 44 | if (empty($config['logLevel'])) { 45 | $config['logLevel'] = 'DEBUG'; 46 | } 47 | 48 | $logLevel = constant('\Psr\Log\LogLevel::'.$config['logLevel']); 49 | 50 | $logFile = $this->getLogFile($monitoringItem, $config); 51 | $this->streamHandler = new StreamHandler($logFile, $logLevel); 52 | 53 | if ($config['simpleLogFormat'] ?? false) { 54 | $this->streamHandler->setFormatter(new \Monolog\Formatter\LineFormatter(self::LOG_FORMAT_SIMPLE)); 55 | } 56 | } 57 | 58 | return $this->streamHandler; 59 | } 60 | 61 | /** 62 | * @param $monitoringItem MonitoringItem 63 | * @param array $config 64 | * 65 | * @return string 66 | * 67 | * @throws \JsonException 68 | */ 69 | public function getLogFile(MonitoringItem $monitoringItem, array $config): string 70 | { 71 | $dir = \Elements\Bundle\ProcessManagerBundle\ElementsProcessManagerBundle::getLogDir().'email/' . $monitoringItem->getId() ; 72 | 73 | if (!is_dir($dir)) { 74 | $filesystem = new Filesystem(); 75 | $filesystem->mkdir($dir, 0755); 76 | } 77 | 78 | return $dir . ('/'.md5(json_encode($config, JSON_THROW_ON_ERROR)).'.log'); 79 | } 80 | 81 | /** 82 | * @param array $loggerConfig 83 | * 84 | * @return array 85 | */ 86 | public function handleShutdown(MonitoringItem $monitoringItem, array $loggerConfig): array 87 | { 88 | 89 | $logFile = $this->getLogFile($monitoringItem, $loggerConfig); 90 | if ($file = is_file($logFile)) { 91 | $mail = new \Pimcore\Mail(); 92 | $mail->subject($loggerConfig['subject']); 93 | 94 | $to = array_filter(explode(';', (string) $loggerConfig['to'])); 95 | if ($to !== []) { 96 | foreach ($to as &$email) { 97 | $email = trim($email); 98 | $mail->addTo($email); 99 | } 100 | 101 | $mail->text($loggerConfig['text']."\n\n\nProcess ID: ".$monitoringItem->getId()."\nERROR LOG:\n" . file_get_contents($logFile)); 102 | $mail->send(); 103 | } 104 | unlink($logFile); 105 | } 106 | 107 | return ['reportedDate' => date('Y-m-d H:i:s')]; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Executor/Logger/File.php: -------------------------------------------------------------------------------- 1 | $actionData 24 | * 25 | * @return string 26 | */ 27 | public function getGridLoggerHtml(MonitoringItem $monitoringItem, array $actionData): string 28 | { 29 | $logFile = $this->getLogFile($actionData, $monitoringItem); 30 | if (is_readable($logFile)) { 31 | $icon = ($actionData['icon'] ?? null) ?: '/bundles/pimcoreadmin/img/flat-color-icons/file-border.svg'; 32 | $title = ($actionData['title'] ?? null) ?: 'File Logger'; 33 | 34 | return 'Download'; 39 | } 40 | 41 | return ''; 42 | } 43 | 44 | /** 45 | * @param array $config 46 | * @param MonitoringItem $monitoringItem 47 | * 48 | * @return StreamHandler|null 49 | */ 50 | public function createStreamHandler(array $config, MonitoringItem $monitoringItem): ?StreamHandler 51 | { 52 | if (!$this->streamHandler) { 53 | if (empty($config['logLevel'])) { 54 | $config['logLevel'] = 'DEBUG'; 55 | } 56 | $logLevel = constant('\Psr\Log\LogLevel::' . $config['logLevel']); 57 | $logFile = $this->getLogFile($config, $monitoringItem); 58 | 59 | if (!array_key_exists('maxFileSizeMB', $config)) { 60 | $config['maxFileSizeMB'] = null; 61 | } 62 | 63 | if (php_sapi_name() === 'cli' && $config['maxFileSizeMB'] && is_readable($logFile)) { 64 | $logFileSize = round(filesize($logFile) / 1024 / 1024); 65 | 66 | /** 67 | * remove half of the data until the file size is within the range 68 | */ 69 | while ($logFileSize > $config['maxFileSizeMB']) { 70 | clearstatcache(); //clear php internal cache otherwise the size won't be correct 71 | 72 | $monitoringItem->getLogger()->notice('Log file size exceeded. Filesize: ' . $logFileSize . 'MB. Max file size: ' . $config['maxFileSizeMB'] . '. Removing old data.'); 73 | $data = explode("\n", file_get_contents($logFile)); 74 | $data = array_slice($data, count($data) / 2); 75 | file_put_contents($logFile, implode("\n", $data)); 76 | $logFileSize = round(filesize($logFile) / 1024 / 1024); 77 | $monitoringItem->getLogger()->notice('New file size: ' . $logFileSize . 'MB.'); 78 | \Pimcore::collectGarbage(); 79 | } 80 | } 81 | 82 | $this->streamHandler = new StreamHandler($logFile, $logLevel); 83 | 84 | if ($config['simpleLogFormat'] ?? false) { 85 | $this->streamHandler->setFormatter(new \Monolog\Formatter\LineFormatter(self::LOG_FORMAT_SIMPLE)); 86 | } 87 | } 88 | 89 | return $this->streamHandler; 90 | } 91 | 92 | /** 93 | * @param array $config 94 | * @param $monitoringItem MonitoringItem 95 | * 96 | * @return string 97 | */ 98 | public function getLogFile(array $config, MonitoringItem $monitoringItem) 99 | { 100 | return ($v = $config['filepath'] ?? null) ? PIMCORE_PROJECT_ROOT . $v : $monitoringItem->getLogFile(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Executor/PimcoreCommand.php: -------------------------------------------------------------------------------- 1 | getValues()['commandOptions'] ?? ''; 33 | $options = str_replace('|', '', trim((string) $options)); 34 | $command = Console::getPhpCli() . ' ' . realpath(PIMCORE_PROJECT_ROOT . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'console') . ' ' . $this->getValues()['command']; 35 | 36 | if ($options !== '' && $options !== '0') { 37 | $command .= ' ' . $options; 38 | } 39 | 40 | if ($monitoringItem instanceof \Elements\Bundle\ProcessManagerBundle\Model\MonitoringItem) { 41 | $commands = \Pimcore::getKernel()->getContainer()->get(CommandsValidator::class)->getValidCommands(); 42 | 43 | if (!array_key_exists($this->getValues()['command'], $commands)) { 44 | throw new Exception('Invalid command - not in valid commands'); 45 | } 46 | /** 47 | * @var \Pimcore\Console\AbstractCommand $commandObject 48 | */ 49 | $commandObject = $commands[$this->getValues()['command']]; 50 | 51 | if ($commandObject->getDefinition()->hasOption('monitoring-item-id')) { 52 | $command .= ' --monitoring-item-id='.$monitoringItem->getId(); 53 | } 54 | 55 | if ($monitoringItem->getParentId()) { 56 | $command .= ' --monitoring-item-parent-id='.$monitoringItem->getParentId(); 57 | } 58 | } 59 | 60 | return $command; 61 | } 62 | 63 | public function validateConfiguration(Configuration $configuration): void 64 | { 65 | 66 | if ($configuration->getExecutorSettings()) { 67 | $settings = $configuration->getExecutorSettingsAsArray(); 68 | $values = $settings['values']; 69 | if (!$values['command']) { 70 | throw new Exception('Please provide a command.'); 71 | } 72 | $commandValidator = \Pimcore::getKernel()->getContainer()->get(CommandsValidator::class); 73 | $commands = \Pimcore::getKernel()->getContainer()->get(CommandsValidator::class)->getValidCommands(); 74 | $commandValidator->validateCommandConfiguration($commands[$values['command']], $configuration); 75 | 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Helper.php: -------------------------------------------------------------------------------- 1 | $callbackSettings 19 | * @param int $userId 20 | * @param mixed $metaData 21 | * @param mixed $parentMonitoringItemId 22 | * @param callable|null $callback 23 | * 24 | * @return array 25 | * 26 | */ 27 | public static function executeJob(string $configId, array $callbackSettings = [], int $userId = 0, mixed $metaData = [], mixed $parentMonitoringItemId = null, ?callable $callback = null) 28 | { 29 | try { 30 | $config = Configuration::getById($configId); 31 | 32 | if (!$config instanceof \Elements\Bundle\ProcessManagerBundle\Model\Configuration) { 33 | $config = new Configuration(); 34 | $config->setExecutorClass(Executor\PimcoreCommand::class); 35 | } 36 | 37 | $executor = $config->getExecutorClassObject(); 38 | $uniqueExecution = $executor->getValues()['uniqueExecution'] ?? false; 39 | if ($uniqueExecution && is_null($parentMonitoringItemId)) { 40 | $running = $config->getRunningProcesses(); 41 | if ($running !== []) { 42 | $msg = "Can't start the process because " . count($running) . ' process is running (ID: ' . $running[0]->getId() . '). Please wait until this processes is finished.'; 43 | 44 | throw new \Exception($msg); 45 | } 46 | } 47 | 48 | $monitoringItem = new MonitoringItem(); 49 | $monitoringItem->setName($config->getName()); 50 | $monitoringItem->setStatus($monitoringItem::STATUS_INITIALIZING); 51 | $monitoringItem->setConfigurationId($config->getId()); 52 | $monitoringItem->setCallbackSettings($callbackSettings); 53 | $monitoringItem->setExecutedByUser($userId); 54 | $monitoringItem->setActions($executor->getActions()); 55 | $monitoringItem->setLoggers($executor->getLoggers()); 56 | $monitoringItem->setMetaData($metaData); 57 | $monitoringItem->setParentId($parentMonitoringItemId); 58 | 59 | if (($executorSettings = $config->getExecutorSettings()) !== '' && ($executorSettings = $config->getExecutorSettings()) !== '0') { 60 | $executorData = json_decode((string) $config->getExecutorSettings(), true, 512, JSON_THROW_ON_ERROR); 61 | 62 | if ($executorData['values']) { 63 | $hideMonitoringItem = $executorData['values']['hideMonitoringItem'] ?? false; 64 | if ($hideMonitoringItem == 'on') { 65 | $monitoringItem->setPublished(false); 66 | } 67 | $monitoringItem->setGroup($executorData['values']['group']); 68 | $monitoringItem->setDeleteAfterHours(isset($executorData['values']['deleteAfterHours']) ? (int)$executorData['values']['deleteAfterHours'] : null); 69 | } 70 | } 71 | 72 | if ($callback) { 73 | $callback($monitoringItem, $executor); 74 | } 75 | $monitoringItem->setMessengerPending(true); 76 | $item = $monitoringItem->save(); 77 | 78 | $command = $executor->getCommand($callbackSettings, $monitoringItem); 79 | $command = escapeshellcmd($command); //prevent os command injection 80 | 81 | putenv(ElementsProcessManagerBundle::MONITORING_ITEM_ENV_VAR . '=' . $item->getId()); 82 | 83 | if (!$executor->getIsShellCommand()) { 84 | $monitoringItem->setCommand($command)->save(); 85 | } else { 86 | $monitoringItem->setCommand($command)->save(); 87 | $command = $executor->getShellCommand($monitoringItem); 88 | } 89 | $messageBus = \Pimcore::getContainer()->get('messenger.bus.pimcore-core'); 90 | $message = new ExecuteCommandMessage($command, $monitoringItem->getId()); 91 | $messageBus->dispatch($message); 92 | 93 | return ['success' => true, 'executedCommand' => $command, 'monitoringItemId' => $item->getId()]; 94 | } catch (\Exception $e) { 95 | return ['success' => false, 'message' => $e->getMessage()]; 96 | } 97 | } 98 | 99 | /** 100 | * @param \Pimcore\Model\User $user 101 | * 102 | * @return array 103 | * 104 | * @throws \Doctrine\DBAL\Exception 105 | */ 106 | public static function getAllowedConfigIdsByUser(\Pimcore\Model\User $user) 107 | { 108 | if (!$user->isAdmin()) { 109 | $roles = $user->getRoles(); 110 | if ($roles === []) { 111 | $roles[] = 'no_result'; 112 | } 113 | $c = []; 114 | foreach ($roles as $r) { 115 | $c[] = 'restrictToRoles LIKE "%,' . $r . ',%" '; 116 | } 117 | $c = ' (restrictToRoles = "" OR (' . implode(' OR ', $c) . ')) '; 118 | 119 | if ($user->getPermissions()) { 120 | 121 | $permissionConditions = []; 122 | foreach ($user->getPermissions() as $permission) { 123 | $permissionConditions[] = 'restrictToPermissions LIKE "%,' . $permission . ',%" '; 124 | } 125 | $c .= ' AND (restrictToPermissions = "" OR (' . implode(' OR ', $permissionConditions) . ')) '; 126 | } 127 | 128 | return \Pimcore\Db::get()->fetchFirstColumn('SELECT id FROM ' . Configuration\Listing\Dao::getTableName() . ' WHERE ' . $c); 129 | } else { 130 | return \Pimcore\Db::get()->fetchFirstColumn('SELECT id FROM ' . Configuration\Listing\Dao::getTableName()); 131 | } 132 | } 133 | 134 | /** 135 | * @param MonitoringItem $monitoringItem 136 | * @param bool $preventModificationDateUpdate 137 | * 138 | * @return MonitoringItem 139 | * 140 | * @throws \JsonException 141 | */ 142 | public static function executeMonitoringItemLoggerShutdown(MonitoringItem $monitoringItem, $preventModificationDateUpdate = false): MonitoringItem 143 | { 144 | $loggers = $monitoringItem->getLoggers(); 145 | 146 | foreach ((array)$loggers as $i => $loggerConfig) { 147 | $loggerClass = $loggerConfig['class']; 148 | if (!class_exists($loggerClass)) { 149 | continue; 150 | } 151 | $logObj = new $loggerClass; 152 | if (method_exists($logObj, 'handleShutdown')) { 153 | $result = $logObj->handleShutdown($monitoringItem, $loggerConfig); 154 | if ($result) { 155 | $loggers[$i] = array_merge($loggerConfig, $result); 156 | } 157 | } 158 | } 159 | $monitoringItem->setLoggers($loggers)->save(); 160 | 161 | return $monitoringItem; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/Installer.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | protected array $permissions = [ 21 | Enums\Permissions::VIEW, 22 | Enums\Permissions::CONFIGURE, 23 | Enums\Permissions::EXECUTE, 24 | ]; 25 | 26 | public function install(): void 27 | { 28 | $this->createPermissions(); 29 | $this->createTables(); 30 | parent::install(); 31 | } 32 | 33 | protected function createPermissions(): void 34 | { 35 | foreach ($this->permissions as $permissionKey) { 36 | Definition::create($permissionKey); 37 | } 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function needsReloadAfterInstall(): bool 44 | { 45 | return true; 46 | } 47 | 48 | protected function getDb(): Connection 49 | { 50 | return \Pimcore\Db::get(); 51 | } 52 | 53 | protected function createTables(): void 54 | { 55 | $db = $this->getDb(); 56 | 57 | $db->executeQuery( 58 | 'CREATE TABLE IF NOT EXISTS `' . ElementsProcessManagerBundle::TABLE_NAME_CONFIGURATION . "` ( 59 | `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 60 | `creationDate` INT(10) UNSIGNED NOT NULL DEFAULT '0', 61 | `modificationDate` INT(10) UNSIGNED NOT NULL DEFAULT '0', 62 | `name` VARCHAR(255) NOT NULL, 63 | `group` VARCHAR(50) NULL DEFAULT NULL, 64 | `description` VARCHAR(255) NULL DEFAULT NULL, 65 | `executorClass` VARCHAR(500) NOT NULL, 66 | `cronJob` TEXT NULL, 67 | `lastCronJobExecution` INT(10) UNSIGNED NULL DEFAULT NULL, 68 | `active` TINYINT(4) NULL DEFAULT '1', 69 | `keepVersions` INT(10) UNSIGNED NULL DEFAULT NULL, 70 | `executorSettings` TEXT NULL, 71 | `restrictToRoles` VARCHAR(100) NOT NULL DEFAULT '', 72 | `action` TEXT NULL, 73 | PRIMARY KEY (`id`), 74 | UNIQUE INDEX `name` (`name`) 75 | ) 76 | ENGINE=InnoDB DEFAULT CHARSET=utf8 77 | "); 78 | 79 | $db->executeQuery( 80 | 'CREATE TABLE IF NOT EXISTS `' . ElementsProcessManagerBundle::TABLE_NAME_MONITORING_ITEM . "` ( 81 | `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, 82 | `parentId` INT(11) NULL DEFAULT NULL, 83 | `creationDate` INT(11) UNSIGNED NOT NULL DEFAULT '0', 84 | `modificationDate` INT(11) UNSIGNED NOT NULL DEFAULT '0', 85 | `reportedDate` INT(10) UNSIGNED NULL DEFAULT NULL, 86 | `currentStep` INT(10) UNSIGNED NULL DEFAULT NULL, 87 | `totalSteps` INT(10) UNSIGNED NULL DEFAULT NULL, 88 | `totalWorkload` INT(10) UNSIGNED NULL DEFAULT NULL, 89 | `currentWorkload` INT(10) UNSIGNED NULL DEFAULT NULL, 90 | `name` VARCHAR(255) NULL DEFAULT NULL, 91 | `command` LONGTEXT NULL, 92 | `status` VARCHAR(20) NULL DEFAULT NULL, 93 | `updated` INT(11) NULL DEFAULT NULL, 94 | `message` VARCHAR(1000) NULL DEFAULT NULL, 95 | `configurationId` INT(11) NULL DEFAULT NULL, 96 | `pid` INT(11) NULL DEFAULT NULL, 97 | `callbackSettings` LONGTEXT NULL, 98 | `executedByUser` INT(11) NULL DEFAULT '0', 99 | `actions` TEXT NULL, 100 | `loggers` TEXT NULL, 101 | `metaData` LONGTEXT NULL, 102 | `published` TINYINT(4) NOT NULL DEFAULT '1', 103 | `group` VARCHAR(50) NULL DEFAULT NULL, 104 | `hasCriticalError` TINYINT(4) NOT NULL DEFAULT '0', 105 | `deleteAfterHours` INT(10) UNSIGNED NULL DEFAULT NULL, 106 | PRIMARY KEY (`id`), 107 | INDEX `updated` (`updated`), 108 | INDEX `status` (`status`), 109 | INDEX `deleteAfterHours` (`deleteAfterHours`) 110 | ) 111 | ENGINE=InnoDB DEFAULT CHARSET=utf8" 112 | ); 113 | 114 | $db->executeQuery( 115 | 'CREATE TABLE IF NOT EXISTS `' . ElementsProcessManagerBundle::TABLE_NAME_CALLBACK_SETTING . "` ( 116 | `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 117 | `creationDate` INT(10) UNSIGNED NOT NULL DEFAULT '0', 118 | `modificationDate` INT(10) UNSIGNED NOT NULL DEFAULT '0', 119 | `name` VARCHAR(255) NOT NULL, 120 | `description` VARCHAR(255) NULL DEFAULT NULL, 121 | `settings` TEXT NULL, 122 | `type` VARCHAR(255) NOT NULL, 123 | PRIMARY KEY (`id`) 124 | ) 125 | ENGINE=InnoDB DEFAULT CHARSET=utf8;" 126 | ); 127 | } 128 | 129 | public function uninstall(): void 130 | { 131 | $tables = [ 132 | ElementsProcessManagerBundle::TABLE_NAME_CONFIGURATION, 133 | ElementsProcessManagerBundle::TABLE_NAME_MONITORING_ITEM, 134 | ElementsProcessManagerBundle::TABLE_NAME_CALLBACK_SETTING, 135 | ]; 136 | foreach ($tables as $table) { 137 | $this->getDb()->executeQuery('DROP TABLE IF EXISTS ' . $table); 138 | } 139 | 140 | foreach ($this->permissions as $permissionKey) { 141 | $this->getDb()->executeQuery('DELETE FROM users_permission_definitions WHERE ' . $this->getDb()->quoteIdentifier('key').' = :permission', ['permission' => $permissionKey]); 142 | } 143 | 144 | parent::uninstall(); 145 | } 146 | 147 | public function getLastMigrationVersionClassName(): ?string 148 | { 149 | return Version20210428000000::class; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Logger.php: -------------------------------------------------------------------------------- 1 | getCriticalErrorLevel()) && in_array($level, $check)) { 20 | $monitoringItem->setHasCriticalError(true)->save(); 21 | } 22 | } 23 | 24 | public function closeLoggerHandlers(): void 25 | { 26 | 27 | /** 28 | * @var \Monolog\Logger $logger 29 | */ 30 | foreach ($this->loggers as $logger) { 31 | foreach ($logger->getHandlers() as $handler) { 32 | $handler->close(); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Maintenance/MaintenanceTask.php: -------------------------------------------------------------------------------- 1 | renderingEngine = $renderingEngine; 26 | } 27 | 28 | public function execute(): void 29 | { 30 | if (!$this->installer->isInstalled()) { 31 | return; 32 | } 33 | 34 | $config = ElementsProcessManagerBundle::getConfiguration(); 35 | if ($config['general']['executeWithMaintenance']) { 36 | ElementsProcessManagerBundle::initProcessManager( 37 | null, 38 | ElementsProcessManagerBundle::getMaintenanceOptions() 39 | ); 40 | $maintenance = new Maintenance($this->renderingEngine); 41 | $maintenance->execute(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Message/CheckCommandAliveMessage.php: -------------------------------------------------------------------------------- 1 | monitoringItemId; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Message/ExecuteCommandMessage.php: -------------------------------------------------------------------------------- 1 | command; 22 | } 23 | 24 | public function getMonitoringItemId(): int 25 | { 26 | return $this->monitoringItemId; 27 | } 28 | 29 | public function getOutputFile(): ?string 30 | { 31 | return $this->outputFile; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Message/StopProcessMessage.php: -------------------------------------------------------------------------------- 1 | monitoringItemId; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/MessageHandler/CheckCommandAliveHandler.php: -------------------------------------------------------------------------------- 1 | getMonitoringItemId())) { 21 | if (!$pid = $monitoringItem->getPid()) { 22 | return; 23 | } 24 | 25 | $checks = 0; 26 | while (in_array(MonitoringItem::getById($monitoringItem->getId())->getStatus(), [MonitoringItem::STATUS_INITIALIZING, MonitoringItem::STATUS_UNKNOWN])) { //check for state because shortly after the background execution the process is alive... 27 | $this->checkPid($monitoringItem); 28 | usleep(500000); 29 | $checks++; 30 | if ($checks > 3) { 31 | break; //just to make sure we do not end in a endlessloop 32 | } 33 | } 34 | 35 | $this->checkPid($monitoringItem); 36 | } 37 | } 38 | 39 | protected function checkPid(MonitoringItem $monitoringItem): void 40 | { 41 | if (!$pid = $monitoringItem->getPid()) { 42 | return; 43 | } 44 | 45 | if (!$this->pidExists($pid)) { 46 | $monitoringItem->setPid(null)->getLogger()->debug('PID' . $pid.' does not exist - removing pid'); 47 | $monitoringItem->save(); 48 | } 49 | } 50 | 51 | protected function pidExists(int $pid): bool 52 | { 53 | if (function_exists('posix_getpgid')) { 54 | return posix_getpgid($pid); 55 | } else { 56 | return file_exists('/proc/'.$pid); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/MessageHandler/ExecuteCommandHandler.php: -------------------------------------------------------------------------------- 1 | getCommand(), $message->getOutputFile()); 21 | if ($monitoringItem = MonitoringItem::getById($message->getMonitoringItemId())) { 22 | $monitoringItem 23 | ->setMessengerPending(false) 24 | ->setPid($pid) 25 | ->save(); 26 | 27 | $monitoringItem->getLogger()->info('Execution Command: ' . $message->getCommand() . ' in Background'); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/MessageHandler/StopProcessHandler.php: -------------------------------------------------------------------------------- 1 | getMonitoringItemId())) { 21 | if (!$pid = $monitoringItem->getPid()) { 22 | return; 23 | } 24 | 25 | $monitoringItem->setPid(null)->setStatus(MonitoringItem::STATUS_FAILED)->save(); 26 | $process = Process::fromShellCommandline('kill -9 '.$pid); 27 | $process->run(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/MetaDataFile.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | protected array $data; 25 | 26 | public function getIdentifier(): string 27 | { 28 | return $this->identifier; 29 | } 30 | 31 | public function setIdentifier(string $identifier): self 32 | { 33 | $this->identifier = $identifier; 34 | 35 | return $this; 36 | } 37 | 38 | /** 39 | * @return array 40 | */ 41 | public function getData(): array 42 | { 43 | return $this->data; 44 | } 45 | 46 | /** 47 | * @param array $data 48 | * 49 | */ 50 | public function setData(array $data): self 51 | { 52 | $this->data = $data; 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * @param string $identifier 59 | * 60 | * @return string 61 | */ 62 | protected static function getFile(string $identifier): string 63 | { 64 | $datFile = PIMCORE_PRIVATE_VAR . '/process-manager-meta-data-files/'; 65 | $filesystem = new Filesystem(); 66 | $filesystem->mkdir($datFile, 0775); 67 | 68 | return $datFile . "$identifier.json"; 69 | } 70 | 71 | final public function __construct() 72 | { 73 | } 74 | 75 | /** 76 | * Unique identifier for the file 77 | * 78 | * @param string $identifier 79 | * 80 | * @throws \JsonException 81 | */ 82 | public static function getById(string $identifier): self 83 | { 84 | if (!isset(self::$instances[$identifier])) { 85 | 86 | $tmp = new static(); 87 | $tmp->setIdentifier($identifier); 88 | 89 | $file = self::getFile($identifier); 90 | $data = file_exists($file) ? json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR) : []; 91 | $tmp->setData($data); 92 | self::$instances[$identifier] = $tmp; 93 | } 94 | 95 | return self::$instances[$identifier]; 96 | } 97 | 98 | public function delete(): void 99 | { 100 | $file = self::getFile($this->getIdentifier()); 101 | if (is_file($file)) { 102 | @unlink($file); 103 | } 104 | } 105 | 106 | /** 107 | * @throws \Exception 108 | */ 109 | public function save(): void 110 | { 111 | $data = $this->getData(); 112 | if (empty($data)) { 113 | throw new \Exception('No data to save '); 114 | } 115 | $check = file_put_contents(self::getFile($this->getIdentifier()), json_encode($this->getData(), JSON_PRETTY_PRINT)); 116 | 117 | if (!$check) { 118 | throw new \Exception("Can't write file: " . $this->getIdentifier()); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Migrations/Version20210428000000.php: -------------------------------------------------------------------------------- 1 | getTable('bundle_process_manager_configuration'); 30 | 31 | $this->addSql('ALTER TABLE `bundle_process_manager_configuration` MODIFY COLUMN `id` varchar(190)'); 32 | if (!$configurationTable->hasIndex('id')) { 33 | $this->addSql('ALTER TABLE `bundle_process_manager_configuration` ADD UNIQUE INDEX `id` (`id`)'); 34 | } 35 | 36 | $this->addSql('ALTER TABLE `bundle_process_manager_configuration` DROP PRIMARY KEY'); 37 | $this->addSql('ALTER TABLE `bundle_process_manager_configuration` ADD PRIMARY KEY (`id`)'); 38 | $this->addSql('ALTER TABLE `bundle_process_manager_monitoring_item` MODIFY COLUMN `configurationId` varchar(190)'); 39 | if ($configurationTable->hasIndex('name')) { 40 | $this->addSql('ALTER TABLE `bundle_process_manager_configuration` DROP KEY `name`'); 41 | } 42 | } 43 | 44 | /** 45 | * @param Schema $schema 46 | */ 47 | public function down(Schema $schema): void 48 | { 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Migrations/Version20230207000000.php: -------------------------------------------------------------------------------- 1 | getTable('bundle_process_manager_configuration'); 27 | 28 | if (!$configurationTable->hasColumn('restrictToPermissions')) { 29 | $this->addSql( 30 | 'ALTER TABLE `bundle_process_manager_configuration` ADD `restrictToPermissions` MEDIUMTEXT DEFAULT ""' 31 | ); 32 | \Pimcore\Cache::clearTags(['system', 'resource']); 33 | } 34 | } 35 | 36 | /** 37 | * @param Schema $schema 38 | */ 39 | public function down(Schema $schema): void 40 | { 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Migrations/Version20230217000000.php: -------------------------------------------------------------------------------- 1 | addSql( 28 | 'UPDATE bundle_process_manager_configuration SET `restrictToPermissions` = "" WHERE `restrictToPermissions` IS null' 29 | ); 30 | $this->addSql( 31 | 'ALTER TABLE `bundle_process_manager_configuration` MODIFY `restrictToPermissions` MEDIUMTEXT NOT NULL DEFAULT ""' 32 | ); 33 | } 34 | 35 | /** 36 | * @param Schema $schema 37 | */ 38 | public function down(Schema $schema): void 39 | { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Migrations/Version20230321092750.php: -------------------------------------------------------------------------------- 1 | addSql( 29 | 'ALTER TABLE bundle_process_manager_monitoring_item ADD `messengerPending` TINYINT(4) NOT NULL DEFAULT "0" after `published`' 30 | ); 31 | 32 | } 33 | 34 | public function down(Schema $schema): void 35 | { 36 | // this down() migration is auto-generated, please modify it to your needs 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Migrations/Version20230425000002.php: -------------------------------------------------------------------------------- 1 | setOrder('DESC'); 32 | $list->setOrderKey('id'); 33 | foreach ($list->load() as $item) { 34 | $isDirty = false; 35 | if ($actions = $item->getActions()) { 36 | foreach ($actions as $key => $action) { 37 | if (!array_key_exists('executeAtStates', $action)) { 38 | $actions[$key]['executeAtStates'] = ['finished']; 39 | $isDirty = true; 40 | } 41 | } 42 | } 43 | if ($isDirty) { 44 | $item->setActions($actions); 45 | $item->save(); 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * @param Schema $schema 52 | */ 53 | public function down(Schema $schema): void 54 | { 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Migrations/Version20241211095632.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE `bundle_process_manager_monitoring_item` MODIFY COLUMN `currentStep` int(10)'); 30 | $this->addSql('ALTER TABLE `bundle_process_manager_monitoring_item` MODIFY COLUMN `totalSteps` int(10)'); 31 | } 32 | 33 | /** 34 | * @param Schema $schema 35 | */ 36 | public function down(Schema $schema): void 37 | { 38 | $this->addSql('ALTER TABLE `bundle_process_manager_monitoring_item` MODIFY COLUMN `currentStep` smallint(5)'); 39 | $this->addSql('ALTER TABLE `bundle_process_manager_monitoring_item` MODIFY COLUMN `totalSteps` smallint(5)'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Model/CallbackSetting.php: -------------------------------------------------------------------------------- 1 | id; 37 | } 38 | 39 | public function setId(?int $id): self 40 | { 41 | $this->id = $id; 42 | 43 | return $this; 44 | } 45 | 46 | public function getName(): string 47 | { 48 | return $this->name; 49 | } 50 | 51 | public function setName(string $name): self 52 | { 53 | $this->name = $name; 54 | 55 | return $this; 56 | } 57 | 58 | public function getDescription(): string 59 | { 60 | return $this->description; 61 | } 62 | 63 | public function setDescription(string $description): self 64 | { 65 | $this->description = $description; 66 | 67 | return $this; 68 | } 69 | 70 | public function getSettings(): string 71 | { 72 | return $this->settings; 73 | } 74 | 75 | public function setSettings(string $settings): self 76 | { 77 | $this->settings = $settings; 78 | 79 | return $this; 80 | } 81 | 82 | public static function getById(int $id): ?self 83 | { 84 | $self = new self(); 85 | /** 86 | * @var CallbackSetting|null $model 87 | */ 88 | $model = $self->getDao()->getById($id); 89 | 90 | return $model; 91 | } 92 | 93 | public function getType(): string 94 | { 95 | return $this->type; 96 | } 97 | 98 | public function setType(string $type): self 99 | { 100 | $this->type = $type; 101 | 102 | return $this; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Model/CallbackSetting/Dao.php: -------------------------------------------------------------------------------- 1 | getValidStorageValues(); 27 | if (!$data['modificationDate']) { 28 | $data['modificationDate'] = time(); 29 | } 30 | if (!$data['creationDate']) { 31 | $data['creationDate'] = time(); 32 | } 33 | if (empty($data['id'])) { 34 | $this->db->insert($this->getTableName(), $data); 35 | $this->model->setId($this->db->lastInsertId($this->getTableName())); 36 | } else { 37 | $this->db->update($this->getTableName(), $data, ['id' => $this->model->getId()]); 38 | } 39 | 40 | /** 41 | * @var CallbackSetting $model 42 | */ 43 | $model = $this->getById($this->model->getId()); 44 | 45 | return $model; 46 | } 47 | 48 | public function delete(): void 49 | { 50 | $id = $this->model->getId(); 51 | 52 | if ($id !== 0) { 53 | $this->db 54 | ->prepare('DELETE FROM ' . $this->getTableName() . ' WHERE `id` = ?') 55 | ->executeQuery([$id]); 56 | 57 | $this->model = null; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Model/CallbackSetting/Listing.php: -------------------------------------------------------------------------------- 1 | getTableName().$this->getCondition().$this->getOrder().$this->getOffsetLimit(); 29 | $ids = $this->db->fetchFirstColumn($sql, $this->model->getConditionVariables()); 30 | 31 | $items = []; 32 | foreach ($ids as $id) { 33 | $items[] = CallbackSetting::getById($id); 34 | } 35 | 36 | $this->model->setData($items); 37 | 38 | return $items; 39 | } 40 | 41 | public function getTotalCount(): int 42 | { 43 | return (int)$this->db->fetchOne( 44 | 'SELECT COUNT(*) as amount FROM '.$this->getTableName().' '.$this->getCondition(), 45 | $this->model->getConditionVariables() 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Model/Configuration/Dao.php: -------------------------------------------------------------------------------- 1 | model->getId(); 28 | 29 | if (!is_null($id)) { 30 | $items = (new MonitoringItem\Listing()) 31 | ->setCondition('configurationId = ?', [$id]) 32 | ->load(); 33 | 34 | foreach ($items as $item) { 35 | $item->delete(); 36 | } 37 | 38 | $this->db 39 | ->prepare('DELETE FROM ' . $this->getTableName() . ' WHERE `id` = ?') 40 | ->executeQuery([$id]); 41 | } 42 | } 43 | 44 | /** 45 | * @param array $params 46 | * 47 | * @throws \Doctrine\DBAL\Exception 48 | * @throws \JsonException 49 | */ 50 | public function save(array $params = []): ?Configuration 51 | { 52 | $data = $this->getValidStorageValues(); 53 | 54 | if ($data['keepVersions'] === '') { 55 | $data['keepVersions'] = null; 56 | } 57 | if (!$data['id']) { 58 | throw new \Exception('A valid Command has to have an id associated with it!'); 59 | } 60 | 61 | $quoteKeyData= []; 62 | array_walk($data, function ($value, $key) use (&$quoteKeyData): void { $quoteKeyData['`'.$key.'`'] = $value; }); 63 | 64 | if (isset($params['oldId'])) { 65 | if ($params['oldId'] !== '') { 66 | $this->db->update($this->getTableName(), $quoteKeyData, ['id' => $params['oldId']]); 67 | } else { 68 | $this->db->insert($this->getTableName(), $quoteKeyData); 69 | } 70 | } elseif ($id = $this->getById($id = $this->model->getId())) { 71 | $this->db->update($this->getTableName(), $quoteKeyData, ['id' => $this->model->getId()]); 72 | } else { 73 | $this->db->insert($this->getTableName(), $quoteKeyData); 74 | } 75 | 76 | return $this->getById($this->model->getId()); 77 | } 78 | 79 | public function getById(mixed $id): ?Configuration 80 | { 81 | /** 82 | * @var Configuration|null $model 83 | */ 84 | $model = parent::getById($id); 85 | if ($model) { 86 | $className = $model->getExecutorClass(); 87 | if ($className) { 88 | $class = new $className; 89 | $class->setDataFromResource($model); 90 | } 91 | 92 | return $model; 93 | } 94 | 95 | return null; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Model/Configuration/Listing.php: -------------------------------------------------------------------------------- 1 | user; 35 | } 36 | 37 | /** 38 | * @return $this 39 | */ 40 | public function setUser(?\Pimcore\Model\User $user): static 41 | { 42 | $this->user = $user; 43 | 44 | return $this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Model/Configuration/Listing/Dao.php: -------------------------------------------------------------------------------- 1 | 29 | * 30 | * @throws Exception 31 | */ 32 | public function load(): array 33 | { 34 | $items = []; 35 | 36 | $ids = $this->loadIdList(); 37 | foreach ($ids as $id) { 38 | $items[] = Configuration::getById($id); 39 | } 40 | 41 | $this->model->setData($items); 42 | 43 | return $items; 44 | } 45 | 46 | public function getTotalCount(): int 47 | { 48 | return (int)$this->db->fetchOne('SELECT COUNT(*) as amount FROM ' . static::getTableName() . ' ' . $this->getCondition(), $this->model->getConditionVariables()); 49 | } 50 | 51 | /** 52 | * @return array 53 | * 54 | * @throws \Doctrine\DBAL\Exception 55 | */ 56 | public function loadIdList(): array 57 | { 58 | $condition = $this->getCondition(); 59 | $conditionVariables = $this->model->getConditionVariables(); 60 | $types = []; 61 | 62 | if ($user = $this->model->getUser()) { 63 | $ids = Helper::getAllowedConfigIdsByUser($user); 64 | $condition .= $condition !== '' && $condition !== '0' ? ' AND ' : ' WHERE '; 65 | if ($ids) { 66 | $condition .= ' id IN('. implode(',', wrapArrayElements($ids, "'")).')'; 67 | } else { 68 | $condition .= 'id IS NULL'; 69 | } 70 | } 71 | 72 | return $this->db->fetchFirstColumn('SELECT id FROM ' . static::getTableName() . $condition . $this->getOrder() . $this->getOffsetLimit(), $conditionVariables, $types); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Model/Dao/AbstractDao.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | protected array $validColumns = []; 19 | 20 | /** 21 | * Get the valid columns from the database 22 | * 23 | * @return void 24 | */ 25 | public function init(): void 26 | { 27 | 28 | $tableName = $this->getTableName(); 29 | $this->validColumns = $this->getValidTableColumns($tableName); 30 | } 31 | 32 | abstract protected function getTableName(): string; 33 | 34 | /** 35 | * @return array 36 | */ 37 | protected function getValidStorageValues(): array 38 | { 39 | $data = []; 40 | foreach ($this->model->getObjectVars() as $key => $value) { 41 | if (in_array($key, $this->validColumns)) { 42 | if (is_object($value)) { 43 | $value = $value::class; 44 | } elseif (is_array($value)) { 45 | foreach ($value as $k => $v) { 46 | if (is_object($v) && method_exists($v, 'getStorageData')) { 47 | $value[$k] = $v->getStorageData(); 48 | } 49 | } 50 | $value = json_encode($value, JSON_THROW_ON_ERROR); 51 | 52 | } elseif (is_bool($value)) { 53 | $value = (int)$value; 54 | } 55 | $data[$key] = $value; 56 | } 57 | } 58 | if (empty($data['creationDate'])) { 59 | $data['creationDate'] = time(); 60 | } 61 | if (empty($data['modificationDate'])) { 62 | $data['modificationDate'] = time(); 63 | } 64 | 65 | return $data; 66 | } 67 | 68 | /** 69 | * @return AbstractModel|null 70 | * 71 | * @throws Exception 72 | */ 73 | public function getById(mixed $id): ?AbstractModel 74 | { 75 | $data = $this->db->fetchAssociative('SELECT * FROM ' . $this->getTableName() . ' WHERE id= :id', ['id' => $id]); 76 | if (!$data) { 77 | return null; 78 | } 79 | $data = $this->convertDataFromRecourse($data); 80 | $this->model->setValues($data); 81 | 82 | return $this->model; 83 | } 84 | 85 | /** 86 | * @param array $data 87 | * 88 | * @return array 89 | */ 90 | public function convertDataFromRecourse(array $data): array 91 | { 92 | return $data; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Model/MonitoringItem/Dao.php: -------------------------------------------------------------------------------- 1 | model->getIsDummy()) { 51 | return $this->model; 52 | } 53 | 54 | /** 55 | * If we are in a transction we need to create a new connection and use this because otherwise 56 | * the satus won't be updated as the transaction isn't committed (e.g when a exception is thrown within a transaction) 57 | */ 58 | if (Db::get()->isTransactionActive()) { 59 | if (!self::$dbTransactionFree instanceof Connection) { 60 | self::$dbTransactionFree = DriverManager::getConnection($this->db->getParams()); 61 | } 62 | $db = self::$dbTransactionFree; 63 | } else { 64 | $db = $this->db; 65 | } 66 | 67 | // refresh db connection if connection has been lost (happens when db connection has been idle for too long) 68 | // if(false == $db->ping()) { 69 | // $db = \Doctrine\DBAL\DriverManager::getConnection($this->db->getParams()); 70 | // } 71 | 72 | $data = $this->getValidStorageValues(); 73 | if (!$preventModificationDateUpdate) { 74 | $data['modificationDate'] = time(); 75 | } 76 | $quoteKeyData = []; 77 | array_walk($data, function ($value, $key) use (&$quoteKeyData): void { 78 | $quoteKeyData['`' . $key . '`'] = $value; 79 | }); 80 | if (empty($quoteKeyData['`id`'])) { 81 | $db->insert($this->getTableName(), $quoteKeyData); 82 | $this->model->setId($db->lastInsertId($this->getTableName())); 83 | } else { 84 | $result = $db->update($this->getTableName(), $quoteKeyData, ['id' => $this->model->getId()]); 85 | } 86 | 87 | return $this->getById($this->model->getId()); 88 | } 89 | 90 | public function delete(): void 91 | { 92 | $id = $this->model->getId(); 93 | 94 | if ($id !== 0) { 95 | foreach ((array)$this->model->getActions() as $action) { 96 | if ($class = $action['class']) { 97 | /** 98 | * @var AbstractAction $class 99 | */ 100 | $class = new $class(); 101 | $class->preMonitoringItemDeletion($this->model, $action); 102 | } 103 | } 104 | 105 | $this->db 106 | ->prepare('DELETE FROM ' . $this->getTableName() . ' WHERE `id` = ?') 107 | ->executeQuery([$id]); 108 | 109 | if ($logFile = $this->model->getLogFile()) { 110 | @unlink($logFile); 111 | } 112 | 113 | recursiveDelete(UploadManger::getUploadDir($id)); 114 | 115 | $this->model = null; 116 | } 117 | } 118 | 119 | /** 120 | * @param array $data 121 | * 122 | * @return array 123 | */ 124 | public function convertDataFromRecourse(array $data): array 125 | { 126 | if ($data['metaData']) { 127 | $data['metaData'] = json_decode($data['metaData'], true); 128 | } 129 | 130 | return $data; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Model/MonitoringItem/Listing.php: -------------------------------------------------------------------------------- 1 | user; 40 | } 41 | 42 | /** 43 | * @return $this 44 | */ 45 | public function setUser(?\Pimcore\Model\User $user) 46 | { 47 | $this->user = $user; 48 | 49 | return $this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Model/MonitoringItem/Listing/Dao.php: -------------------------------------------------------------------------------- 1 | model->getCondition()) !== '' && ($cond = $this->model->getCondition()) !== '0') { 31 | $condition .= ' WHERE ' . $cond . ' '; 32 | } 33 | 34 | /** 35 | * @var \Elements\Bundle\ProcessManagerBundle\Model\MonitoringItem\Listing $list 36 | */ 37 | $list = $this->model; 38 | if (($user = $list->getUser()) && !$user->isAdmin()) { 39 | if ($ids = Helper::getAllowedConfigIdsByUser($user)) { 40 | if ($this->model->getCondition() !== '' && $this->model->getCondition() !== '0') { 41 | $condition .= ' AND '; 42 | } else { 43 | $condition .= ' WHERE '; 44 | } 45 | $condition .= ' configurationId IN(' . implode(', ', wrapArrayElements($ids, "'")).')'; 46 | } 47 | } 48 | 49 | return $condition; 50 | } 51 | 52 | /** 53 | * @return array 54 | * 55 | * @throws \Doctrine\DBAL\Exception 56 | */ 57 | public function load(): array 58 | { 59 | $sql = 'SELECT id FROM ' . $this->getTableName() . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(); 60 | $ids = $this->db->fetchFirstColumn($sql, $this->model->getConditionVariables()); 61 | 62 | $items = []; 63 | foreach ($ids as $id) { 64 | $item = MonitoringItem::getById($id); 65 | if ($item) {//hack because somehow it can happen that we dont get a monitoring id if we are using multiprocessing and the element would be empty 66 | $items[] = $item; 67 | } 68 | } 69 | 70 | $this->model->setData($items); 71 | 72 | return $items; 73 | } 74 | 75 | public function getTotalCount(): int 76 | { 77 | return (int) $this->db->fetchOne('SELECT COUNT(*) as amount FROM ' . $this->getTableName() . ' '. $this->getCondition(), $this->model->getConditionVariables()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine_migrations.yml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | 'Elements\Bundle\ProcessManagerBundle\Migrations': '%kernel.project_dir%/vendor/elements/process-manager-bundle/src/Migrations' -------------------------------------------------------------------------------- /src/Resources/config/maintenance.yml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: true 4 | autoconfigure: true 5 | public: false 6 | -------------------------------------------------------------------------------- /src/Resources/config/pimcore/config.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | messenger: 3 | transports: 4 | elements_process_manager: 5 | dsn: 'sync://' 6 | 7 | routing: 8 | Elements\Bundle\ProcessManagerBundle\Message\ExecuteCommandMessage: elements_process_manager 9 | Elements\Bundle\ProcessManagerBundle\Message\CheckCommandAliveMessage: elements_process_manager 10 | Elements\Bundle\ProcessManagerBundle\Message\StopProcessMessage: elements_process_manager -------------------------------------------------------------------------------- /src/Resources/config/pimcore/routing.yml: -------------------------------------------------------------------------------- 1 | app: 2 | resource: "@ElementsProcessManagerBundle/Controller/" 3 | type: annotation 4 | -------------------------------------------------------------------------------- /src/Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: 'maintenance.yml' } 3 | 4 | services: 5 | _defaults: 6 | autowire: true 7 | autoconfigure: true 8 | public: false 9 | 10 | Elements\Bundle\ProcessManagerBundle\Installer: 11 | public: true 12 | arguments: 13 | $bundle: "@=service('kernel').getBundle('ElementsProcessManagerBundle')" 14 | 15 | # auto-register all commands as services 16 | Elements\Bundle\ProcessManagerBundle\Command\: 17 | resource: '../../Command' 18 | 19 | Elements\Bundle\ProcessManagerBundle\Service\: 20 | resource: '../../Service' 21 | 22 | Elements\Bundle\ProcessManagerBundle\Controller\: 23 | resource: '../../Controller' 24 | public: true 25 | tags: ['controller.service_arguments'] 26 | 27 | Elements\Bundle\ProcessManagerBundle\MessageHandler\: 28 | resource: '../../MessageHandler' 29 | 30 | Elements\Bundle\ProcessManagerBundle\Service\CommandsValidator: 31 | public: true 32 | arguments : 33 | $strategy: "default" 34 | $whiteList : [] 35 | $blackList : ["process-manager:maintenance"] 36 | 37 | Elements\Bundle\ProcessManagerBundle\SystemEventsListener: ~ 38 | 39 | ##Executor classes 40 | Elements\Bundle\ProcessManagerBundle\Executor\PimcoreCommand: 41 | tags: 42 | - { name: "elements.processManager.executorClasses" } 43 | Elements\Bundle\ProcessManagerBundle\Executor\ClassMethod: 44 | tags: 45 | - { name: "elements.processManager.executorClasses" } 46 | 47 | ##Logger classes 48 | Elements\Bundle\ProcessManagerBundle\Executor\Logger\File: 49 | tags: 50 | - { name: "elements.processManager.executorLoggerClasses" } 51 | Elements\Bundle\ProcessManagerBundle\Executor\Logger\Console: 52 | tags: 53 | - { name: "elements.processManager.executorLoggerClasses" } 54 | Elements\Bundle\ProcessManagerBundle\Executor\Logger\Application: 55 | tags: 56 | - { name: "elements.processManager.executorLoggerClasses" } 57 | Elements\Bundle\ProcessManagerBundle\Executor\Logger\EmailSummary: 58 | tags: 59 | - { name: "elements.processManager.executorLoggerClasses" } 60 | 61 | ##Action classes 62 | Elements\Bundle\ProcessManagerBundle\Executor\Action\Download: 63 | tags: 64 | - { name: "elements.processManager.executorActionClasses" } 65 | Elements\Bundle\ProcessManagerBundle\Executor\Action\OpenItem: 66 | tags: 67 | - { name: "elements.processManager.executorActionClasses" } 68 | Elements\Bundle\ProcessManagerBundle\Executor\Action\JsEvent: 69 | tags: 70 | - { name: "elements.processManager.executorActionClasses" } 71 | 72 | ##Callback classes 73 | executionNote: 74 | class : Elements\Bundle\ProcessManagerBundle\Executor\Callback\General 75 | arguments : 76 | $name : "executionNote" 77 | $extJsClass : "pimcore.plugin.processmanager.executor.callback.executionNote" 78 | $jsFile : "/bundles/elementsprocessmanager/js/executor/callback/executionNote.js" 79 | tags: 80 | - { name: "elements.processManager.executorCallbackClasses" } -------------------------------------------------------------------------------- /src/Resources/public/css/admin.css: -------------------------------------------------------------------------------- 1 | .plugin_pmicon { 2 | background: url(/bundles/pimcoreadmin/img/flat-color-icons/cable_release.svg) center center no-repeat !important; 3 | } 4 | 5 | .plugin_pmicon_header:before { 6 | position: absolute; 7 | width: 16px; 8 | height: 16px; 9 | bottom: 0px; 10 | right: 0px; 11 | content: ""; 12 | background: url(/bundles/pimcoreadmin/img/flat-color-icons/cable_release.svg) center center no-repeat !important; 13 | } 14 | 15 | .pimcore_version_6 .plugin_pmicon_header:before { 16 | width: 24px; 17 | height: 24px; 18 | } 19 | 20 | .pm_icon_cli { 21 | background: url(/bundles/pimcoreadmin/img/flat-color-icons/go.svg) center center no-repeat !important; 22 | } 23 | .process-manager-notification-progress-bar { 24 | /* height:30px;*/ 25 | } 26 | .plugin-process-manager-item-status-finished { 27 | color: #006402; 28 | font-weight: bold; 29 | } 30 | 31 | .plugin-process-manager-item-status-failed { 32 | color: darkred; 33 | font-weight: bold; 34 | } 35 | 36 | .process-manager-notification-progress-bar.finished, 37 | .process-manager-notification-progress-bar.finished .x-progress-bar { 38 | background-color: #9ACD32; 39 | } 40 | .process-manager-notification-progress-bar.finished_with_errors , 41 | .process-manager-notification-progress-bar.finished_with_errors .x-progress-bar , 42 | .process-manager-notification-progress-bar.failed, 43 | .process-manager-notification-progress-bar.failed .x-progress-bar 44 | { 45 | background-color: #FF6347; 46 | } 47 | 48 | #processmanager_active_processes .process-manager-notification-progress-bar.failed .x-progress-text-back, 49 | #processmanager_active_processes .process-manager-notification-progress-bar.finished_with_errors .x-progress-text-back, 50 | #processmanager_active_processes .process-manager-notification-progress-bar.finished .x-progress-text-back 51 | { 52 | color:#fff; 53 | } 54 | .plugin-process-manager-save-button{ 55 | background: url("/bundles/pimcoreadmin/img/flat-color-icons/ok.svg") no-repeat scroll left center transparent !important; 56 | } 57 | .plugin-process-manager-execute-button{ 58 | background: url("/bundles/pimcoreadmin/img/flat-color-icons/go.svg") no-repeat scroll left center transparent !important; 59 | } 60 | 61 | .process_manager_icon_download { 62 | text-decoration: none; 63 | width: 27px; 64 | border-radius: 3px; 65 | height: 21px; 66 | display: inline-block; 67 | vertical-align: top; 68 | } 69 | 70 | .process_manager_action_js_event { 71 | margin-right: 5px; 72 | text-align: center; 73 | } 74 | .process_manager_action_js_event img { 75 | 76 | } 77 | 78 | /* 79 | .process_manager_icon_action_overlay_go { 80 | position: absolute; 81 | width: 14px; 82 | height: 14px; 83 | bottom: 0px; 84 | right: 0px; 85 | content: ""; 86 | background: url(/bundles/pimcoreadmin/img/flat-color-icons/overlay-go.svg) center center no-repeat !important; 87 | }*/ 88 | 89 | .process_manager_icon_action_open { 90 | position: relative; 91 | text-decoration: none; 92 | width: 22px; 93 | border-radius: 3px; 94 | height: 21px; 95 | display: inline-block; 96 | } 97 | 98 | .process_manager_icon_action_open.document { 99 | background-image: url("/bundles/pimcoreadmin/img/flat-color-icons/document.svg"); 100 | } 101 | .process_manager_icon_action_open.object { 102 | background-image: url("/bundles/pimcoreadmin/img/flat-color-icons/object.svg"); 103 | } 104 | .process_manager_icon_action_open.asset { 105 | background-image: url("/bundles/pimcoreadmin/img/flat-color-icons/asset.svg"); 106 | } 107 | .process-manager-callback-settings-table { 108 | border : 1px solid #ECECEC; 109 | border-collapse: collapse; 110 | } 111 | .process-manager-callback-settings-table th, .process-manager-callback-settings-table td{ 112 | border : 1px solid #ECECEC; 113 | padding: 2px; 114 | font-size: 0.9em; 115 | } 116 | .process-manager-callback-settings-tabl th { 117 | text-align: left; 118 | } 119 | .process-manager-link a { 120 | color: #739BC8; 121 | } 122 | 123 | .process-manager-hide-icon { 124 | display: none; 125 | } 126 | 127 | /* dark background under tabs */ 128 | /* Very specific selector to only target main tab-bar in the window */ 129 | #pimcore_plugin_pm_panel-bodyWrap > .x-tab-bar > .x-tab-bar-body { 130 | background-color: #3c3f41; 131 | } 132 | 133 | .pm_tooltip_icon { 134 | position: relative; 135 | left:3px; 136 | top:3px; 137 | } 138 | .pm_number_select .x-form-trigger-wrap-default{ 139 | height: auto; 140 | vertical-align: top; 141 | } 142 | 143 | .plugin_pm_property_selector_panel_source { 144 | border-top: 1px solid #d0d0d0; 145 | border-right: 1px solid #d0d0d0; 146 | } 147 | .plugin_pm_property_selector_panel_target { 148 | border-top: 1px solid #d0d0d0; 149 | } 150 | .plugin_pm_property_selector_panel { 151 | margin-bottom: 10px; 152 | } 153 | 154 | #processmanager_active_processes-body { 155 | /*overflow-y: scroll;*/ 156 | } 157 | 158 | .process-manager-active-processes-header-text { 159 | margin-bottom: 2px; 160 | display: block; 161 | } 162 | .process-manager-active-processes-header-text .x-form-display-field { 163 | min-height: auto; 164 | } 165 | .pm-hide { 166 | background-color: #ffae00; 167 | } 168 | 169 | a.process-manager-details-link { 170 | color: #404040 !important; 171 | } 172 | .x-tool-process-manager-unpublish-all { 173 | background-image: url(/bundles/pimcoreadmin/img/flat-color-icons/approve.svg) !important; 174 | background-size: contain; 175 | background-color: #3c3f41; 176 | } 177 | 178 | .active_processes_finished_icon { 179 | width: 15px; 180 | height: 15px; 181 | position: relative; 182 | top:4px; 183 | } 184 | 185 | .process-manager-action-icon-tbar { 186 | height: 18px; 187 | position: relative; 188 | top: 4px; 189 | } 190 | 191 | #pimcore_plugin_pm_panel { 192 | background: none !important; 193 | } 194 | -------------------------------------------------------------------------------- /src/Resources/public/css/logFileLogger.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #000; 3 | margin-top: 40px; 4 | font-size: 13px; 5 | color: #fff; 6 | 7 | font-family: "Open Sans", "Helvetica Neue", helvetica, arial, verdana, sans-serif; 8 | } 9 | 10 | h1 { 11 | font-size: 18px; 12 | color: #f00; 13 | font-weight: bold; 14 | margin-bottom: 0px; 15 | } 16 | 17 | .reload { 18 | position: fixed; 19 | top: 0; 20 | left: 0; 21 | width: 100%; 22 | font-family: "Open Sans", "Helvetica Neue", helvetica, arial, verdana, sans-serif; 23 | background-color: #ECECEC; 24 | padding: 5px; 25 | padding-left: 10px; 26 | margin-bottom: 0px; 27 | } 28 | 29 | .reload { 30 | color: #404040; 31 | } 32 | 33 | .reload label { 34 | position: relative; 35 | top: -2px; 36 | } 37 | 38 | .reloadWrapper { 39 | float: left; 40 | padding-right: 5px; 41 | } 42 | 43 | #content { 44 | padding-bottom: 20px; 45 | position: relative; 46 | } 47 | 48 | .reload label:hover { 49 | cursor: pointer; 50 | } -------------------------------------------------------------------------------- /src/Resources/public/img/sprite-open-item-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valantic-at/ProcessManager/6e4a0c9d10ab51b65d05196d4d2e4be51c41a56f/src/Resources/public/img/sprite-open-item-action.png -------------------------------------------------------------------------------- /src/Resources/public/js/executor/action/abstractAction.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.action.abstractAction"); 2 | pimcore.plugin.processmanager.executor.action.abstractAction = Class.create(pimcore.plugin.processmanager.helper.form,{ 3 | values : {}, 4 | getTopBar: function (niceName, id) { 5 | return [{ 6 | xtype: "tbtext", 7 | text: " " + niceName + "" 8 | }, 9 | "->", 10 | { 11 | iconCls: "pimcore_icon_delete", 12 | handler: this.removeForm.bind(this, id) 13 | } 14 | ]; 15 | }, 16 | 17 | setValues : function(values){ 18 | this.values = values; 19 | }, 20 | 21 | removeForm: function (id) { 22 | Ext.getCmp('plugin_pm_action_panel').remove(Ext.getCmp(id)); 23 | }, 24 | 25 | getFieldValue: function (fieldName) { 26 | if(this.values){ 27 | return this.values[fieldName]; 28 | } 29 | }, 30 | 31 | addForm : function(){ 32 | Ext.getCmp('plugin_pm_action_panel').add(this.getForm()); 33 | this.form.updateLayout(); 34 | Ext.getCmp('plugin_pm_action_panel').updateLayout(); 35 | return this.form; 36 | }, 37 | 38 | /** 39 | * Implement in extended class if needed 40 | */ 41 | executeActionForActiveProcessList : function () { 42 | 43 | } 44 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/action/download.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.action.download"); 2 | pimcore.plugin.processmanager.executor.action.download = new Class.create(pimcore.plugin.processmanager.executor.action.abstractAction,{ 3 | 4 | getButton : function(){ 5 | this.button = { 6 | icon: '/bundles/pimcoreadmin/img/flat-color-icons/download-cloud.svg', 7 | text: t("plugin_pm_download"), 8 | "handler" : this.addForm.bind(this) 9 | } 10 | return this.button; 11 | }, 12 | 13 | getForm : function(){ 14 | if(!this.button){ 15 | this.getButton(); 16 | } 17 | var myId = Ext.id(); 18 | 19 | let items = []; 20 | items.push(this.getTextField('accessKey',{mandatory : true,tooltip : t('plugin_pm_tooltip_accessKey')})); 21 | items.push(this.getTextField('label')); 22 | items.push(this.getTextField('filepath',{mandatory: true})); 23 | items.push(this.getCheckbox('deleteWithMonitoringItem',{ 24 | fieldLabel : t('plugin_pm_action_download_delete_with_monitoring_item'), 25 | inputValue : true 26 | })); 27 | items.push(this.getTextField('class',{hidden: true,value : '\\Elements\\Bundle\\ProcessManagerBundle\\Executor\\Action\\Download'})); 28 | this.form = new Ext.form.FormPanel({ 29 | forceLayout: true, 30 | id: myId, 31 | type : 'formPanel', 32 | style: "margin: 10px", 33 | bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", 34 | tbar: this.getTopBar(this.button.text,myId), 35 | items: items 36 | }); 37 | 38 | return this.form; 39 | }, 40 | 41 | executeActionForActiveProcessList : function(actionButtonPanel,actionData,monitoringItem,obj,index){ 42 | 43 | if(actionData.executeAtStates.includes(monitoringItem.status)){ 44 | 45 | let text = actionData.label ? actionData.label : t('download'); 46 | let disabled = false; 47 | 48 | if(actionData.dynamicData.fileExists == false){ 49 | text += ' (' + t('plugin_pm_download_file_doesnt_exist') + ')'; 50 | disabled = true; 51 | } 52 | let button = Ext.create('Ext.Button', { 53 | text: text, 54 | icon : "/bundles/pimcoreadmin/img/flat-color-icons/download-cloud.svg", 55 | style: (index > 0 ? 'margin-left:5px;' : ''), 56 | disabled : disabled, 57 | scale: 'small', 58 | handler: function() { 59 | processmanagerPlugin.download(monitoringItem.id,actionData.accessKey); 60 | } 61 | }); 62 | actionButtonPanel.items.add(button); 63 | } 64 | } 65 | 66 | }); 67 | 68 | document.addEventListener('processManager.monitoringItemGrid', (e) => { 69 | e.preventDefault(); 70 | let currentTarget = e.detail.sourceEvent.currentTarget; 71 | if(e.detail.trigger === 'download'){ 72 | let accessKey = e.detail.sourceEvent.currentTarget.getAttribute('data-process-manager-access-key'); 73 | processmanagerPlugin.download(e.detail.monitoringId,accessKey); 74 | } 75 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/action/jsEvent.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.action.jsEvent"); 2 | pimcore.plugin.processmanager.executor.action.jsEvent = new Class.create(pimcore.plugin.processmanager.executor.action.abstractAction,{ 3 | 4 | getButton : function(){ 5 | this.button = { 6 | icon: '/bundles/pimcoreadmin/img/flat-color-icons/biohazard.svg', 7 | text: t("plugin_pm_jsEvent"), 8 | "handler" : this.addForm.bind(this) 9 | } 10 | return this.button; 11 | }, 12 | 13 | getForm : function(){ 14 | if(!this.button){ 15 | this.getButton(); 16 | } 17 | let myId = Ext.id(); 18 | let items = []; 19 | items.push(this.getTextField('label')); 20 | items.push(this.getTextField('icon')); 21 | items.push(this.getTextField('eventName',{mandatory : true})); 22 | items.push(this.getTextArea('eventData',{tooltip : t('plugin_pm_eventData_tooltip')})); 23 | items.push(this.getTextField('class',{hidden: true,value : '\\Elements\\Bundle\\ProcessManagerBundle\\Executor\\Action\\JsEvent'})); 24 | this.form = new Ext.form.FormPanel({ 25 | forceLayout: true, 26 | id: myId, 27 | type : 'formPanel', 28 | style: "margin: 10px", 29 | bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", 30 | tbar: this.getTopBar(this.button.text,myId), 31 | items: items 32 | }); 33 | 34 | return this.form; 35 | }, 36 | 37 | executeActionForActiveProcessList : function(actionButtonPanel,actionData,monitoringItem,obj,index){ 38 | let detail = { 39 | source : 'activeProcessList', 40 | monitoringItem : monitoringItem, 41 | actionData : actionData, 42 | actionButtonPanel : actionButtonPanel, 43 | obj : obj, 44 | index : index 45 | } 46 | const event = new CustomEvent(actionData.eventName, {detail: detail}); 47 | document.dispatchEvent(event); 48 | }, 49 | 50 | executeActionForGridList : function (data) { 51 | let detail = { 52 | source : 'gridList', 53 | monitoringItem : data.monitoringItem, 54 | actionData : data.actionData 55 | }; 56 | 57 | const event = new CustomEvent(data.actionData.eventName, {detail: detail}); 58 | document.dispatchEvent(event); 59 | } 60 | }); 61 | 62 | var processmanagerPluginJsEvent = new pimcore.plugin.processmanager.executor.action.jsEvent(); 63 | 64 | document.addEventListener('processManager.monitoringItemGrid', (e) => { 65 | e.preventDefault(); 66 | let currentTarget = e.detail.sourceEvent.currentTarget; 67 | if(e.detail.trigger === 'jsEvent'){ 68 | let eventName = currentTarget.getAttribute('data-process-manager-event-name'); 69 | let actionData = {}; 70 | for(let i = 0; i < e.detail.monitoringItemData.actions.length; i++){ 71 | if(e.detail.monitoringItemData.actions[i].eventName === eventName){ 72 | actionData = e.detail.monitoringItemData.actions[i]; 73 | } 74 | } 75 | processmanagerPluginJsEvent.executeActionForGridList({ 76 | monitoringItem : e.detail.monitoringItemData, 77 | actionData : actionData, 78 | sourceEvent : e 79 | }); 80 | } 81 | 82 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/action/openItem.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.action.openItem"); 2 | pimcore.plugin.processmanager.executor.action.openItem = new Class.create(pimcore.plugin.processmanager.executor.action.abstractAction,{ 3 | 4 | getButton : function(){ 5 | this.button = { 6 | icon: '/bundles/pimcoreadmin/img/flat-color-icons/open_window.svg', 7 | text: t("plugin_pm_open_item"), 8 | "handler" : this.addForm.bind(this) 9 | } 10 | return this.button; 11 | }, 12 | 13 | getForm : function(){ 14 | if(!this.button){ 15 | this.getButton(); 16 | } 17 | let myId = Ext.id(); 18 | 19 | 20 | let items = []; 21 | items.push(this.getTextField('label')); 22 | items.push(this.getSelectField('type', { 23 | store : [ 24 | ['object', t('plugin_pm_type_object')], 25 | ['document', t('plugin_pm_type_document')], 26 | ['asset', t('plugin_pm_type_asset')] 27 | ] 28 | })); 29 | items.push(this.getNumberField('itemId')); 30 | items.push(this.getTextField('class',{hidden: true,value : '\\Elements\\Bundle\\ProcessManagerBundle\\Executor\\Action\\OpenItem'})); 31 | 32 | this.form = new Ext.form.FormPanel({ 33 | forceLayout: true, 34 | id: myId, 35 | type : 'formPanel', 36 | style: "margin: 10px", 37 | bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", 38 | tbar: this.getTopBar(this.button.text,myId), 39 | items: items 40 | }); 41 | 42 | return this.form; 43 | }, 44 | 45 | executeActionForActiveProcessList : function(actionButtonPanel,actionData,monitoringItem,obj,index){ 46 | if(actionData.executeAtStates.includes(monitoringItem.status)){ 47 | let icons = { 48 | document : "/bundles/pimcoreadmin/img/flat-white-icons/page.svg", 49 | object : "/bundles/pimcoreadmin/img/flat-white-icons/object.svg", 50 | asset : "/bundles/pimcoreadmin/img/flat-white-icons/camera.svg" 51 | }; 52 | let text = actionData.label ? actionData.label : t('open'); 53 | 54 | let button = Ext.create('Ext.Button', { 55 | text: text, 56 | icon : icons[actionData.type], 57 | disabled : !actionData.dynamicData.item_exists, 58 | iconCls : ' pimcore_icon_overlay_go ', 59 | style: (index > 0 ? 'margin-left:5px;' : ''), 60 | scale: 'small', 61 | handler: function() { 62 | let type = actionData.type; 63 | if(type === 'asset'){ 64 | pimcore.helpers.openAsset(actionData.itemId,actionData.dynamicData.item_type); 65 | } 66 | if(type === 'object'){ 67 | pimcore.helpers.openObject(actionData.itemId,actionData.dynamicData.item_type); 68 | } 69 | if(type === 'document'){ 70 | pimcore.helpers.openDocument(actionData.itemId,actionData.dynamicData.item_type); 71 | } 72 | } 73 | }); 74 | actionButtonPanel.items.add(button); 75 | } 76 | } 77 | 78 | }); 79 | 80 | document.addEventListener('processManager.monitoringItemGrid', (e) => { 81 | e.preventDefault(); 82 | let currentTarget = e.detail.sourceEvent.currentTarget; 83 | if(e.detail.trigger === 'openItem'){ 84 | let itemId = currentTarget.getAttribute('data-process-manager-item-id'); 85 | let itemType = currentTarget.getAttribute('data-process-manager-item-type'); 86 | let actionType = currentTarget.getAttribute('data-process-manager-item-action-type'); 87 | pimcore.helpers["open" + actionType](itemId,itemType); 88 | } 89 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/callback/default.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.callback.default"); 2 | pimcore.plugin.processmanager.executor.callback.default = Class.create(pimcore.plugin.processmanager.executor.callback.abstractCallback, { 3 | name: "default" 4 | 5 | 6 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/callback/example.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.callback.example"); 2 | pimcore.plugin.processmanager.executor.callback.example = Class.create(pimcore.plugin.processmanager.executor.callback.abstractCallback, { 3 | //a custom name 4 | name: "example", 5 | 6 | // remove this param or set it to "window" if you want that the callback is opened in a modal window instead of a tab 7 | // callbackWindowType: 'tab', 8 | 9 | //change the label width 10 | //labelWidth : 300, 11 | 12 | getFormItems: function () { 13 | var items = []; 14 | 15 | //general properties - supported by all field types 16 | var config = { 17 | mandatory: false, //bool - defines if the field is required 18 | tooltip: t("plugin_pm_myTooltip") //add a tooltip icon for explanations 19 | } 20 | 21 | //Input field 22 | items.push(this.getTextField('myTextField', config)); 23 | 24 | //Textarea field 25 | items.push(this.getTextArea('myTextArea', config)); 26 | 27 | //Number field 28 | items.push(this.getNumberField('myNumber', config)); 29 | 30 | //Date field 31 | items.push(this.getDateField('myDate', config)); 32 | 33 | //Select a pimcore role 34 | items.push(this.getRoleSelection('myRole', config)); 35 | 36 | //Select a pimcore locale 37 | items.push(this.getLocaleSelection('myLocale', config)); 38 | 39 | //Checkbox 40 | items.push(this.getCheckbox('myCheckbox', config)); 41 | 42 | 43 | //File upload field 44 | items.push(this.getUploadField('priceListFile',{ 45 | label : t("file"), 46 | mandatory : true 47 | })); 48 | 49 | 50 | /*Multi select 51 | The URL should return an array like this: 52 | { 53 | "data": [ 54 | { 55 | "key": 345, 56 | "value": "My Display field" 57 | } 58 | ] 59 | } 60 | */ 61 | /* items.push(this.getMultiSelect('classificationIndustry',{ 62 | url : '/admin/app/get-multiselect-options?type=classificationIndustry', 63 | height: 150 64 | }));*/ 65 | 66 | //Select field 67 | var selectConfig = config; 68 | selectConfig.store = [ 69 | ['key1', t('plugin_pm_example_select_1')], 70 | ['key2', t('plugin_pm_example_select_2')] 71 | ]; 72 | items.push(this.getSelectField('mySelectField', selectConfig)); 73 | 74 | //Href field (a itemSelectorConfig can also be passed to limit the selection -> @see items.push(this.getItemSelector('myItems',itemSelectorConfig)); below 75 | items.push(this.getHref('myHref', config)); 76 | 77 | //item Selector 78 | 79 | var itemSelectorConfig = config; 80 | /* 81 | //Add a custom button to the item selector and restrict the selections to objects of the class "Product" 82 | var materialAddButton = { 83 | xtype: "button", 84 | iconCls: "pimcore_icon_add", 85 | handler: function () { 86 | alert('Open Custom window...'); 87 | }.bind(this) 88 | }; 89 | 90 | //values for type: "object","document","asset" 91 | var itemSelectorConfig = { 92 | itemSelectorConfig: {type: ["object"], specific: {classes: ['Product']}}, 93 | buttons : [materialAddButton], 94 | mandatory : true 95 | }; 96 | */ 97 | 98 | items.push(this.getItemSelector('myItems', itemSelectorConfig)); 99 | 100 | var propertySelectorConfig = config; 101 | 102 | var propertySelectorConfig = { 103 | storeUrl: '/admin/elementsprocessmanager/index/property-list', 104 | mandatory: false 105 | /*, 106 | //column config - optional - default to "name" column for display 107 | columns : [ 108 | { 109 | text: 'Id', 110 | sortable: true, 111 | dataIndex: "id" 112 | }, 113 | { 114 | text: t("plugin_pm_property_selector_propertyname"), 115 | sortable: true, 116 | dataIndex: "name", 117 | flex: 1 118 | } 119 | ]*/ 120 | }; 121 | 122 | /* 123 | * The /admin/elementsprocessmanager/index/property-list call returns a array like this 124 | { 125 | "success": true, 126 | "data": [ 127 | { 128 | "id": 1, 129 | "name": "Display text - myProperties - 1" 130 | }, 131 | { 132 | "id": 2, 133 | "name": "Display text - myProperties - 2" 134 | } 135 | }*/ 136 | 137 | items.push(this.getPropertySelector('myProperties', propertySelectorConfig)); 138 | 139 | 140 | return items; 141 | } 142 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/callback/executionNote.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.callback.executionNote"); 2 | pimcore.plugin.processmanager.executor.callback.executionNote = Class.create(pimcore.plugin.processmanager.executor.callback.abstractCallback, { 3 | 4 | name: "executionNote", 5 | 6 | getFormItems: function () { 7 | var items = []; 8 | items.push(this.getTextArea('note')); 9 | return items; 10 | }, 11 | 12 | execute: function () { 13 | this.openConfigWindow(); 14 | } 15 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/class/classMethod.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.class.classMethod"); 2 | pimcore.plugin.processmanager.executor.class.classMethod = Class.create(pimcore.plugin.processmanager.executor.class.abstractExecutor, { 3 | 4 | getFormItems: function () { 5 | var items = this.getDefaultItems(); 6 | items.push(this.getTextField('executorClass')); 7 | items.push(this.getTextField('executorMethod')); 8 | items.push(this.getCheckbox('uniqueExecution')); 9 | items.push(this.getCronjobField()); 10 | items.push(this.getCronjobDescription()); 11 | items.push(this.getNumberField("keepVersions")); 12 | items.push(this.getCheckbox("hideMonitoringItem")); 13 | return items; 14 | } 15 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/class/command.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.class.command"); 2 | pimcore.plugin.processmanager.executor.class.command = Class.create(pimcore.plugin.processmanager.executor.class.abstractExecutor, { 3 | 4 | getFormItems: function () { 5 | var items = this.getDefaultItems(); 6 | items.push(this.getTextField('command')); 7 | items.push(this.getCheckbox('uniqueExecution')); 8 | items.push(this.getCronjobField()); 9 | items.push(this.getCronjobDescription()); 10 | items.push(this.getNumberField("keepVersions")); 11 | items.push(this.getCheckbox("hideMonitoringItem")); 12 | return items; 13 | } 14 | 15 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/class/pimcoreCommand.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.class.pimcoreCommand"); 2 | pimcore.plugin.processmanager.executor.class.pimcoreCommand = Class.create(pimcore.plugin.processmanager.executor.class.abstractExecutor, { 3 | 4 | initialize: function () { 5 | 6 | //this.settings.windowHeight = 800; 7 | }, 8 | 9 | getCommandList: function () { 10 | 11 | let store = []; 12 | 13 | for (let key in processmanagerPlugin.config.pimcoreCommands) { 14 | if (processmanagerPlugin.config.pimcoreCommands.hasOwnProperty(key)) { 15 | store.push([key, key]); 16 | } 17 | } 18 | 19 | this.command = { 20 | xtype: "combo", 21 | editable: false, 22 | name: "command", 23 | labelWidth: this.labelWidth, 24 | value: this.getFieldValue('command'), 25 | store: store, 26 | mode: "local", 27 | width: "100%", 28 | triggerAction: "all", 29 | mandatory: true 30 | } 31 | this.command.fieldLabel = this.getFieldLabel('command',this.command) 32 | return this.command; 33 | }, 34 | 35 | getFormItems: function () { 36 | let items = this.getDefaultItems(); 37 | items.push(this.getCommandList()); 38 | items.push(this.getTextField('commandOptions')); 39 | items.push(this.getCheckbox('uniqueExecution')); 40 | items.push(this.getCronjobField()); 41 | items.push(this.getCronjobDescription()); 42 | items.push(this.getNumberField("keepVersions")); 43 | items.push(this.getNumberField("deleteAfterHours")); 44 | items.push(this.getCheckbox("hideMonitoringItem")); 45 | return items; 46 | } 47 | 48 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/logger/abstractLogger.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.logger.abstractLogger"); 2 | pimcore.plugin.processmanager.executor.logger.abstractLogger = Class.create(pimcore.plugin.processmanager.helper.form, { 3 | values: {}, 4 | getTopBar: function (niceName, id) { 5 | return [{ 6 | xtype: "tbtext", 7 | text: "" + niceName + "" 8 | }, 9 | "->", 10 | { 11 | iconCls: "pimcore_icon_delete", 12 | handler: this.removeForm.bind(this, id) 13 | } 14 | ]; 15 | }, 16 | 17 | setValues: function (values) { 18 | this.values = values; 19 | }, 20 | 21 | removeForm: function (id) { 22 | Ext.getCmp('plugin_pm_logger_panel').remove(Ext.getCmp(id)); 23 | }, 24 | 25 | getFieldValue: function (fieldName) { 26 | if (this.values) { 27 | return this.values[fieldName]; 28 | } 29 | }, 30 | 31 | addForm: function () { 32 | Ext.getCmp('plugin_pm_logger_panel').add(this.getForm()); 33 | this.form.updateLayout(); 34 | Ext.getCmp('plugin_pm_logger_panel').updateLayout(); 35 | return this.form; 36 | }, 37 | 38 | getButton: function () { 39 | this.button = { 40 | iconCls: "pimcore_icon_add", 41 | text: t("plugin_pm_logger_" + this.type), 42 | "handler": this.addForm.bind(this) 43 | } 44 | return this.button; 45 | }, 46 | 47 | stopRefresh: function () { 48 | Ext.TaskManager.stop(pimcore.globalmanager.get("plugin_pm_cnf").monitoringItems.autoRefreshTask); 49 | }, 50 | 51 | startRefresh: function () { 52 | if (pimcore.globalmanager.get("plugin_pm_cnf").monitoringItems.autoRefresh.getValue()) { 53 | Ext.TaskManager.start(pimcore.globalmanager.get("plugin_pm_cnf").monitoringItems.autoRefreshTask); 54 | } 55 | } 56 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/logger/application.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.logger.application"); 2 | pimcore.plugin.processmanager.executor.logger.application = Class.create(pimcore.plugin.processmanager.executor.logger.abstractLogger, { 3 | type: 'application', 4 | 5 | getForm: function () { 6 | if (!this.button) { 7 | this.getButton(); 8 | } 9 | var myId = Ext.id(); 10 | this.form = new Ext.form.FormPanel({ 11 | forceLayout: true, 12 | id: myId, 13 | type: 'formPanel', 14 | style: "margin: 10px", 15 | items: [ 16 | this.getLogLevelField(), 17 | { 18 | xtype: "hidden", 19 | name: "class", 20 | readOnly: true, 21 | value: '\\Elements\\Bundle\\ProcessManagerBundle\\Executor\\Logger\\Application' 22 | } 23 | ], 24 | bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", 25 | tbar: this.getTopBar(this.button.text, myId) 26 | }); 27 | return this.form; 28 | }, 29 | 30 | 31 | showLogs: function (monitoringItemId, loggerIndexPositions) { 32 | this.stopRefresh(); 33 | var url = '/admin/elementsprocessmanager/monitoring-item/log-application-logger?id=' + monitoringItemId + '&loggerIndex=' + loggerIndexPositions; 34 | Ext.Ajax.request({ 35 | url: url, 36 | success: function (response, opts) { 37 | var result = Ext.decode(response.responseText); 38 | if (result.success) { 39 | if (result.success) { 40 | try { 41 | pimcore.globalmanager.get("pimcore_applicationlog_admin").activate(); 42 | } 43 | catch (e) { 44 | pimcore.globalmanager.add("pimcore_applicationlog_admin", new pimcore.bundle.applicationlogger.log.admin()); 45 | } 46 | } 47 | 48 | var formateTime = function (date) { 49 | return Ext.Date.format(date, 'g:i A'); 50 | }; 51 | var applicationLogger = pimcore.globalmanager.get("pimcore_applicationlog_admin"); 52 | applicationLogger.clearValues(); 53 | 54 | var fromDate = new Date((result.data.creationDate - 1 ) * 1000); 55 | applicationLogger.fromDate.setValue(fromDate); 56 | 57 | applicationLogger.fromTime.setValue(fromDate); 58 | 59 | if (!result.data.pid) { 60 | var tillDate = new Date((result.data.modificationDate + 1 ) * 1000); 61 | applicationLogger.toDate.setValue(tillDate); 62 | applicationLogger.toTime.setValue(formateTime(tillDate)); 63 | } 64 | 65 | applicationLogger.searchpanel.getForm().setValues({ 66 | 'component': result.data.name, 67 | 'priority': result.data.logLevel 68 | }); 69 | 70 | applicationLogger.find(); 71 | 72 | if (applicationLogger.autoRefresh && result.data.pid) { 73 | applicationLogger.autoRefresh.setValue(true); 74 | } 75 | } else { 76 | pimcore.helpers.showNotification(t("error"), t("plugin_pm_error_process_manager"), "error", result.message); 77 | } 78 | }.bind(this) 79 | }); 80 | } 81 | }); 82 | var processManagerApplicationLogger = new pimcore.plugin.processmanager.executor.logger.application(); 83 | document.addEventListener('processManager.monitoringItemGrid', (e) => { 84 | e.preventDefault(); 85 | let currentTarget = e.detail.sourceEvent.currentTarget; 86 | if(e.detail.trigger === 'showApplicationLogs'){ 87 | let index = currentTarget.getAttribute('data-process-manager-action-index'); 88 | processManagerApplicationLogger.showLogs(e.detail.monitoringItemData.id,index); 89 | } 90 | }); 91 | -------------------------------------------------------------------------------- /src/Resources/public/js/executor/logger/console.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.logger.console"); 2 | pimcore.plugin.processmanager.executor.logger.console = Class.create(pimcore.plugin.processmanager.executor.logger.abstractLogger, { 3 | type: 'console', 4 | 5 | getForm: function () { 6 | if (!this.button) { 7 | this.getButton(); 8 | } 9 | var myId = Ext.id(); 10 | this.form = new Ext.form.FormPanel({ 11 | forceLayout: true, 12 | id: myId, 13 | type: 'formPanel', 14 | style: "margin: 10px", 15 | items: [ 16 | this.getLogLevelField(), 17 | this.getCheckbox('simpleLogFormat'), 18 | //this.getTextField('accessKey'), 19 | { 20 | xtype: "hidden", 21 | name: "class", 22 | readOnly: true, 23 | value: '\\Elements\\Bundle\\ProcessManagerBundle\\Executor\\Logger\\Console' 24 | } 25 | ], 26 | bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", 27 | tbar: this.getTopBar(this.button.text, myId) 28 | }); 29 | return this.form; 30 | }, 31 | 32 | showLogs: function (monitoringItemId, loggerIndexPositions) { 33 | alert('Asdfasdfa'); 34 | console.log(monitoringItemId); 35 | console.log(loggerIndexPositions); 36 | } 37 | 38 | }); -------------------------------------------------------------------------------- /src/Resources/public/js/executor/logger/emailSummary.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.logger.emailSummary"); 2 | pimcore.plugin.processmanager.executor.logger.emailSummary = Class.create(pimcore.plugin.processmanager.executor.logger.abstractLogger, { 3 | type: 'emailSummary', 4 | 5 | getForm: function () { 6 | if (!this.button) { 7 | this.getButton(); 8 | } 9 | var myId = Ext.id(); 10 | this.form = new Ext.form.FormPanel({ 11 | forceLayout: true, 12 | id: myId, 13 | type: 'formPanel', 14 | style: "margin: 10px", 15 | defaults: { 16 | labelWidth: 120 17 | }, 18 | items: [ 19 | this.getLogLevelField(), 20 | this.getTextField('to',{tooltip : t('plugin_pm_to_tooltip'),'mandatory' : true}), 21 | this.getTextField('subject'), 22 | this.getTextArea('text'), 23 | this.getCheckbox('simpleLogFormat',{checked : true}), 24 | { 25 | xtype: "hidden", 26 | name: "class", 27 | readOnly: true, 28 | value: '\\Elements\\Bundle\\ProcessManagerBundle\\Executor\\Logger\\EmailSummary' 29 | } 30 | ], 31 | bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", 32 | tbar: this.getTopBar(this.button.text, myId) 33 | }); 34 | return this.form; 35 | }, 36 | 37 | showLogs: function (monitoringItemId, loggerIndexPositions) { 38 | return ''; 39 | } 40 | }); 41 | var processManagerFileLogger = new pimcore.plugin.processmanager.executor.logger.emailSummary(); 42 | -------------------------------------------------------------------------------- /src/Resources/public/js/executor/logger/file.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.executor.logger.file"); 2 | pimcore.plugin.processmanager.executor.logger.file = Class.create(pimcore.plugin.processmanager.executor.logger.abstractLogger, { 3 | type: 'file', 4 | 5 | getForm: function () { 6 | if (!this.button) { 7 | this.getButton(); 8 | } 9 | var myId = Ext.id(); 10 | this.form = new Ext.form.FormPanel({ 11 | forceLayout: true, 12 | id: myId, 13 | type: 'formPanel', 14 | style: "margin: 10px", 15 | defaults: { 16 | labelWidth: 120 17 | }, 18 | items: [ 19 | this.getLogLevelField(), 20 | this.getTextField('filepath'), 21 | this.getNumberField('maxFileSizeMB'), 22 | this.getCheckbox('simpleLogFormat'), 23 | { 24 | xtype: "hidden", 25 | name: "class", 26 | readOnly: true, 27 | value: '\\Elements\\Bundle\\ProcessManagerBundle\\Executor\\Logger\\File' 28 | } 29 | ], 30 | bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", 31 | tbar: this.getTopBar(this.button.text, myId) 32 | }); 33 | return this.form; 34 | }, 35 | 36 | showLogs: function (monitoringItemId, loggerIndexPositions) { 37 | //we need to stop the panel reloading to prevent errors when a user clicks and a reload is done 38 | this.stopRefresh(); 39 | var url = '/admin/elementsprocessmanager/monitoring-item/log-file-logger?id=' + monitoringItemId + '&loggerIndex=' + loggerIndexPositions; 40 | var modal = new Ext.Window({ 41 | id: 'processManagerLoggerFileWindow', 42 | title: t('plugin_pm_log_info'), 43 | modal: true, 44 | height: '80%', 45 | width: '90%', 46 | maximizable: true, 47 | items: [{ 48 | xtype: "box", 49 | autoEl: {tag: 'iframe', src: url, width: '100%', height: '100%'} 50 | }] 51 | }); 52 | modal.show(); 53 | this.startRefresh(); 54 | 55 | 56 | } 57 | }); 58 | var processManagerFileLogger = new pimcore.plugin.processmanager.executor.logger.file(); 59 | -------------------------------------------------------------------------------- /src/Resources/public/js/logFileLogger.js: -------------------------------------------------------------------------------- 1 | var timer = null; 2 | $(function () { 3 | if ($('#autorefresh').is(':checked')) { 4 | startRefresh(); 5 | } 6 | 7 | $('#autorefresh').on('change',function () { 8 | if (!this.checked) { 9 | clearTimeout(timer); 10 | } else { 11 | startRefresh(); 12 | } 13 | }); 14 | }); 15 | 16 | 17 | function startRefresh() { 18 | timer = setTimeout(startRefresh, 1000); 19 | $.get(location.href + '&ajax=1', function (data) { 20 | $('#content').html(data.html); 21 | if(!data.monitoringItem.pid){ 22 | $('#autorefresh').attr('checked', false); 23 | clearTimeout(timer); 24 | } 25 | $(window).scrollTop($(document).height()); 26 | }); 27 | } -------------------------------------------------------------------------------- /src/Resources/public/js/panel/general.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.panel.general"); 2 | pimcore.plugin.processmanager.panel.general = Class.create({ 3 | 4 | initialize: function (config) { 5 | config = defaultValue(config, {}); 6 | 7 | if (!this.panel) { 8 | this.configPanel = new pimcore.plugin.processmanager.panel.config(); 9 | this.monitoringItems = new pimcore.plugin.processmanager.panel.monitoringItem(); 10 | 11 | var items = []; 12 | items.push(this.configPanel.getPanel()); 13 | items.push(this.monitoringItems.getPanel()); 14 | 15 | if (processmanagerPlugin.config.executorCallbackClasses) { 16 | this.callbackSettings = new pimcore.plugin.processmanager.panel.callbackSetting(); 17 | items.push(this.callbackSettings.getPanel()); 18 | } 19 | this.panel = new Ext.TabPanel({ 20 | title: t("plugin_pm"), 21 | closable: true, 22 | deferredRender: false, 23 | forceLayout: true, 24 | activeTab: 0, 25 | id: "pimcore_plugin_pm_panel", 26 | iconCls: "plugin_pmicon_header", 27 | items: items 28 | }); 29 | 30 | var tabPanel = Ext.getCmp("pimcore_panel_tabs"); 31 | tabPanel.add(this.panel); 32 | tabPanel.setActiveItem("pimcore_plugin_pm_panel"); 33 | 34 | this.panel.on("destroy", function () { 35 | Ext.TaskManager.stop(this.monitoringItems.autoRefreshTask); 36 | pimcore.globalmanager.remove("plugin_pm_cnf"); 37 | }.bind(this)); 38 | 39 | if (config.activeTab) { 40 | this.panel.setActiveTab(config.activeTab); 41 | } 42 | 43 | pimcore.layout.refresh(); 44 | 45 | } 46 | return this.panel; 47 | } 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /src/Resources/public/js/window/detailwindow.js: -------------------------------------------------------------------------------- 1 | pimcore.registerNS("pimcore.plugin.processmanager.window.detailwindow"); 2 | pimcore.plugin.processmanager.window.detailwindow = Class.create({ 3 | getClassName: function (){ 4 | return "pimcore.plugin.processmanager.window.detailwindow"; 5 | }, 6 | 7 | initialize: function (data) { 8 | this.data = data; 9 | this.getInputWindow(); 10 | this.detailWindow.show(); 11 | }, 12 | 13 | 14 | getInputWindow: function () { 15 | 16 | if(!this.detailWindow) { 17 | this.detailWindow = new Ext.Window({ 18 | width: '80%', 19 | height: '80%', 20 | title: t('plugin_pm_monitoring_item_detail_window'), 21 | closeAction:'close', 22 | plain: true, 23 | maximized: false, 24 | autoScroll: true, 25 | modal: true, 26 | buttons: [ 27 | { 28 | text: t('close'), 29 | iconCls: "pimcore_icon_cancel", 30 | scale: "medium", 31 | handler: function(){ 32 | this.detailWindow.hide(); 33 | this.detailWindow.destroy(); 34 | }.bind(this) 35 | } 36 | ] 37 | }); 38 | 39 | this.createPanel(); 40 | } 41 | return this.detailWindow; 42 | }, 43 | 44 | 45 | createPanel: function() { 46 | var items = []; 47 | items.push({ 48 | xtype: "textfield", 49 | fieldLabel: t("name"), 50 | name: "name", 51 | readOnly: true, 52 | value: this.data.name, 53 | width : "100%" 54 | }); 55 | 56 | items.push({ 57 | xtype: "textfield", 58 | fieldLabel: t("plugin_pm_command"), 59 | name: "command", 60 | readOnly: true, 61 | value: this.data.command, 62 | width : "100%" 63 | }); 64 | 65 | items.push({ 66 | xtype: "textarea", 67 | fieldLabel: t('plugin_pm_message'), 68 | name: "message", 69 | readOnly: true, 70 | value: this.data.message, 71 | width : "100%", 72 | height: 100 73 | }); 74 | 75 | items.push({ 76 | xtype: "textarea", 77 | fieldLabel: t('plugin_pm_callback_settings'), 78 | name: "callbackSettings", 79 | readOnly: true, 80 | value: JSON.stringify(Ext.decode(this.data.callbackSettingsString), null, '\t'), 81 | width : "100%", 82 | height: 250 83 | }); 84 | 85 | items.push({ 86 | xtype: "textarea", 87 | fieldLabel: t('plugin_pm_metaData'), 88 | name: "metaData", 89 | readOnly: true, 90 | value: this.data.metaData ? JSON.stringify(this.data.metaData, null, '\t') : '', 91 | width : "100%", 92 | height: 250 93 | }); 94 | 95 | items.push({ 96 | xtype: "textarea", 97 | fieldLabel: t('plugin_pm_logger_data'), 98 | name: "loggers", 99 | readOnly: true, 100 | value: JSON.stringify(this.data.loggers, null, '\t'), 101 | width : "100%", 102 | height: 250 103 | }); 104 | 105 | items.push({ 106 | xtype: "textarea", 107 | fieldLabel: t('plugin_pm_logger_actions'), 108 | name: "actions", 109 | readOnly: true, 110 | value: JSON.stringify(this.data.actions, null, '\t'), 111 | width : "100%", 112 | height: 250 113 | }); 114 | 115 | items.push({ 116 | xtype: "textfield", 117 | fieldLabel: t("plugin_pm_executedByUser"), 118 | name: "executedByUser", 119 | readOnly: true, 120 | value: this.data.executedByUser, 121 | width : "100%" 122 | }); 123 | 124 | items.push({ 125 | xtype: "textfield", 126 | fieldLabel: t("plugin_pm_group"), 127 | name: "group", 128 | readOnly: true, 129 | value: this.data.group, 130 | width : "100%" 131 | }); 132 | 133 | items.push({ 134 | xtype: "numberfield", 135 | fieldLabel: "ID", 136 | name: "id", 137 | readOnly: true, 138 | value: this.data.id 139 | }); 140 | 141 | items.push({ 142 | xtype: "numberfield", 143 | fieldLabel: "Parent ID", 144 | name: "parentId", 145 | readOnly: true, 146 | value: this.data.parentId 147 | }); 148 | 149 | items.push({ 150 | xtype: "textfield", 151 | fieldLabel: "CID", 152 | name: "cid", 153 | readOnly: true, 154 | value: this.data.configurationId 155 | }); 156 | items.push({ 157 | xtype: "numberfield", 158 | fieldLabel: "PID", 159 | name: "pid", 160 | readOnly: true, 161 | value: this.data.pid 162 | }); 163 | 164 | var dateRenderer = function(d) { 165 | if (d !== undefined && d) { 166 | var date = new Date(d * 1000); 167 | return Ext.Date.format(date, "Y-m-d H:i:s"); 168 | } else { 169 | return ""; 170 | } 171 | }; 172 | 173 | items.push({ 174 | xtype: "textfield", 175 | fieldLabel: t("plugin_pm_monitor_creationDate"), 176 | name: "creationDate", 177 | readOnly: true, 178 | value: dateRenderer(this.data.creationDate) 179 | }); 180 | 181 | items.push({ 182 | xtype: "textfield", 183 | fieldLabel: t("plugin_pm_monitor_modificationDate"), 184 | name: "modificationDate", 185 | readOnly: true, 186 | value: dateRenderer(this.data.modificationDate) 187 | }); 188 | items.push({ 189 | xtype: "textfield", 190 | fieldLabel: t("plugin_pm_monitor_reportedDate"), 191 | name: "reportedDate", 192 | readOnly: true, 193 | value: dateRenderer(this.data.reportedDate) 194 | }); 195 | 196 | items.push({ 197 | xtype: "textfield", 198 | fieldLabel: t("status"), 199 | name: "status", 200 | readOnly: true, 201 | value: this.data.status 202 | }); 203 | 204 | items.push({ 205 | xtype: "textfield", 206 | fieldLabel: t("steps"), 207 | name: "steps", 208 | readOnly: true, 209 | value: this.data.steps 210 | }); 211 | 212 | items.push({ 213 | xtype: "textfield", 214 | fieldLabel: t("plugin_pm_monitor_duration"), 215 | name: "steps", 216 | readOnly: true, 217 | value: this.data.duration 218 | }); 219 | 220 | var panel = new Ext.form.FormPanel({ 221 | border: false, 222 | frame:false, 223 | bodyStyle: 'padding:10px', 224 | items: items, 225 | labelWidth: 130, 226 | collapsible: false, 227 | autoScroll: true 228 | }); 229 | 230 | this.detailWindow.add(panel); 231 | } 232 | 233 | }); -------------------------------------------------------------------------------- /src/Resources/views/MonitoringItem/logFileLogger.html.twig: -------------------------------------------------------------------------------- 1 | {% set ajaxRequest = app.request.get("ajax") %} 2 | {% if ajaxRequest == false %} 3 | 4 | 5 | Logs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | {% endif %} 16 | 17 | {% if monitoringItem.getPid() == false %} 18 |

Process Finished

19 | {% endif %} 20 |
{{ data | raw }}
21 | 22 | {% if ajaxRequest == false %} 23 |
24 | 25 | 26 |
27 | {% if monitoringItem.getPid() %} 28 |
29 | 32 |
33 | {% endif %} 34 |
35 | LogLevel: {{ logLevel }} | Log file: {{ logFile }} 36 |
37 |
38 | {% if monitoringItem.getPid() %} 39 | 40 | {% endif %} 41 | 42 | 43 | {% endif %} 44 | -------------------------------------------------------------------------------- /src/Resources/views/reportEmail.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | ProcessManager Report 4 | 5 | 35 | 36 | 37 | 38 |

ProcessManager report

39 |

The following processes seems to have a problem. Please check them...

40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {% for monitoringItem in reportItems %} 51 | {# @var monitoringItem \Elements\Bundle\ProcessManagerBundle\Model\MonitoringItem #} 52 | 53 | 54 | 55 | 56 | 63 | 64 | 65 | 66 | 67 | {% endfor %} 68 | {% if totalItemsCount > reportItems|length %} 69 | 70 | 71 | 72 | {% endif %} 73 |
IDPIDNameStatusMessageCommandLast update
{{ monitoringItem.getId() }}{{ monitoringItem.getPid() }}{{ monitoringItem.getName() }} 57 | {% if monitoringItem.getStatus() == "failed" %} 58 | {{ monitoringItem.getStatus() }} 59 | {% else %} 60 | {{ monitoringItem.getStatus() }} 61 | {% endif %} 62 | {{ monitoringItem.getMessage() }}{{ monitoringItem.getCommand() }}{{ ('@'~monitoringItem.getModificationDate()) | date("Y-m-d H:i:s") }}
Further {{ totalItemsCount - reportItems|length }} items are considered as failed.
74 | 75 | 76 | -------------------------------------------------------------------------------- /src/Service/CommandsValidator.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | protected array $whiteList = []; 24 | 25 | /** 26 | * @var array 27 | */ 28 | protected array $blackList = []; 29 | 30 | /** 31 | * @param string $strategy 32 | * @param array $whiteList 33 | * @param array $blackList 34 | */ 35 | public function __construct(string $strategy = 'default', array $whiteList = [], array $blackList = []) 36 | { 37 | $this->setStrategy($strategy); 38 | $this->setWhiteList($whiteList); 39 | //add 'debug:translation', 'translation:extract' as they cause issues on Pimcore 11.1.4 40 | $blackList = array_merge($blackList, ['debug:translation', 'translation:extract']); 41 | $this->setBlackList($blackList); 42 | } 43 | 44 | public function validateCommandConfiguration(LazyCommand|Command $command, Configuration $configuration): void 45 | { 46 | 47 | $settings = $configuration->getExecutorSettingsAsArray(); 48 | $values = $settings['values']; 49 | 50 | $commandOptions = $values['commandOptions'] ?? ''; 51 | 52 | if (is_callable([$command, 'validatedCommandOptions'])) { 53 | $command->validatedCommandOptions($commandOptions, $configuration); 54 | } 55 | } 56 | 57 | /** 58 | * @return array 59 | */ 60 | public function getValidCommands(): array 61 | { 62 | 63 | $application = new Application(\Pimcore::getKernel()); 64 | $commands = $this->{'getCommands' . ucfirst($this->getStrategy())}($application->all()); 65 | 66 | ksort($commands); 67 | 68 | return $commands; 69 | } 70 | 71 | /** 72 | * @param array $commands 73 | * 74 | * @return array 75 | */ 76 | protected function getCommandsAll(array $commands): array 77 | { 78 | return $commands; 79 | } 80 | 81 | /** 82 | * @param array $commands 83 | * 84 | * @return array 85 | */ 86 | protected function getCommandsDefault(array $commands): array 87 | { 88 | $validCommands = []; 89 | 90 | /** 91 | * @var Command $command 92 | */ 93 | foreach ($commands as $name => $command) { 94 | if (in_array($name, $this->getBlackList())) { 95 | continue; 96 | } 97 | 98 | if (in_array($name, $this->getWhiteList())) { 99 | $validCommands[$name] = $command; 100 | 101 | continue; 102 | } 103 | 104 | $useTrait = in_array(ExecutionTrait::class, $this->classUsesTraits($command)); 105 | if ($useTrait) { 106 | $validCommands[$name] = $command; 107 | } 108 | } 109 | 110 | return $validCommands; 111 | } 112 | 113 | /** 114 | * @return array 115 | */ 116 | protected function classUsesTraits(LazyCommand|Command $class, bool $autoload = true): array 117 | { 118 | if ($class instanceof LazyCommand) { 119 | $class = $class->getCommand(); 120 | } 121 | $traits = []; 122 | 123 | // Get traits of all parent classes 124 | do { 125 | $traits = array_merge(class_uses($class, $autoload), $traits); 126 | // @phpstan-ignore-next-line 127 | } while ($class = get_parent_class($class)); 128 | 129 | // Get traits of all parent traits 130 | $traitsToSearch = $traits; 131 | while ($traitsToSearch !== []) { 132 | $newTraits = class_uses(array_pop($traitsToSearch), $autoload); 133 | $traits = array_merge($newTraits, $traits); 134 | $traitsToSearch = array_merge($newTraits, $traitsToSearch); 135 | } 136 | 137 | foreach (array_keys($traits) as $trait) { 138 | $traits = array_merge(class_uses($trait, $autoload), $traits); 139 | } 140 | 141 | return array_unique($traits); 142 | } 143 | 144 | public function getStrategy(): string 145 | { 146 | return $this->strategy; 147 | } 148 | 149 | public function setStrategy(string $strategy): static 150 | { 151 | $this->strategy = $strategy; 152 | 153 | return $this; 154 | } 155 | 156 | /** 157 | * @return array 158 | */ 159 | public function getWhiteList(): array 160 | { 161 | return $this->whiteList; 162 | } 163 | 164 | /** 165 | * @param array $whiteList 166 | */ 167 | public function setWhiteList(array $whiteList): static 168 | { 169 | $this->whiteList = $whiteList; 170 | 171 | return $this; 172 | } 173 | 174 | /** 175 | * @return array 176 | */ 177 | public function getBlackList(): array 178 | { 179 | return $this->blackList; 180 | } 181 | 182 | /** 183 | * @param array $blackList 184 | */ 185 | public function setBlackList(array $blackList): static 186 | { 187 | $this->blackList = $blackList; 188 | 189 | return $this; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/Service/UploadManger.php: -------------------------------------------------------------------------------- 1 | files->all(); 18 | 19 | $callbackSettings = $monitoringItem->getCallbackSettings(); 20 | $hasUploads = false; 21 | /** 22 | * @var \Symfony\Component\HttpFoundation\File\UploadedFile $upload 23 | */ 24 | foreach ($files as $key => $upload) { 25 | if ($callbackSettings[$key] ?? null) { 26 | $hasUploads = true; 27 | if ($monitoringItem->getId() === 0) { 28 | $monitoringItem->save(); 29 | } 30 | 31 | $secureFileName = $key . '.dat'; 32 | $uploadDir = self::getUploadDir($monitoringItem->getId()); 33 | $target = $upload->move($uploadDir, $secureFileName); 34 | $callbackSettings[$key] = [ 35 | 'file' => $target->getRealPath(), 36 | 'originalName' => $upload->getClientOriginalName(), 37 | ]; 38 | } 39 | } 40 | if ($hasUploads) { 41 | $monitoringItem->setCallbackSettings($callbackSettings)->save(); 42 | } 43 | } 44 | 45 | public static function getUploadDir(int $id): string 46 | { 47 | return \PIMCORE_SYSTEM_TEMP_DIRECTORY . '/process-manager-uploads/' . $id; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/SystemEventsListener.php: -------------------------------------------------------------------------------- 1 | 'onConsoleError', 27 | ConsoleEvents::TERMINATE => 'onConsoleTerminate', 28 | 29 | ]; 30 | } 31 | 32 | public function onConsoleError(ConsoleErrorEvent $e): void 33 | { 34 | if (!\Pimcore::isInstalled() || !$this->installer->isInstalled()) { 35 | return; 36 | } 37 | 38 | if (($monitoringItem = ElementsProcessManagerBundle::getMonitoringItem()) instanceof \Elements\Bundle\ProcessManagerBundle\Model\MonitoringItem) { 39 | $error = $e->getError(); 40 | $monitoringItem->setMessage('ERROR: ' . $error->getMessage()); 41 | $monitoringItem->getLogger()->error($error->getMessage()); 42 | $monitoringItem->setPid(null)->setStatus($monitoringItem::STATUS_FAILED)->save(); 43 | } 44 | } 45 | 46 | public function onConsoleTerminate(ConsoleTerminateEvent $e): void 47 | { 48 | if (!\Pimcore::isInstalled() || !$this->installer->isInstalled()) { 49 | return; 50 | } 51 | 52 | if (($monitoringItem = ElementsProcessManagerBundle::getMonitoringItem()) instanceof \Elements\Bundle\ProcessManagerBundle\Model\MonitoringItem) { 53 | Helper::executeMonitoringItemLoggerShutdown($monitoringItem); 54 | } 55 | 56 | if ($e->getExitCode() == 0 && ($monitoringItem = ElementsProcessManagerBundle::getMonitoringItem())) { 57 | if (($config = Configuration::getById($monitoringItem->getConfigurationId() ?? '')) instanceof \Elements\Bundle\ProcessManagerBundle\Model\Configuration) { 58 | $versions = $config->getKeepVersions(); 59 | if (is_numeric($versions)) { 60 | $list = new MonitoringItem\Listing(); 61 | $list->setOrder('DESC')->setOrderKey('id')->setOffset((int)$versions)->setLimit( 62 | 100_000_000_000 63 | ); //a limit has to defined otherwise the offset won't work 64 | $list->setCondition( 65 | 'status ="finished" AND configurationId=? AND IFNULL(pid,0) != ? AND parentId IS NULL ', 66 | [$config->getId(), $monitoringItem->getPid()] 67 | ); 68 | 69 | $items = $list->load(); 70 | foreach ($items as $item) { 71 | $item->delete(); 72 | } 73 | } 74 | } 75 | if (!$monitoringItem->getMessage()) { 76 | $monitoringItem->setMessage('finished'); 77 | } 78 | $monitoringItem->setCompleted(); 79 | $monitoringItem->setPid(null)->save(); 80 | } 81 | } 82 | } 83 | --------------------------------------------------------------------------------