From ca82c813991317f89e1102ffd25f6320173435ad Mon Sep 17 00:00:00 2001 From: Roly Rudy Gutierrez Pinto Date: Thu, 24 Sep 2020 08:00:14 -0400 Subject: [PATCH] PMCORE-2178 Migrate to queue job - Cron File: cron.php - Activity: emails --- app/Jobs/TaskScheduler.php | 14 ++ config/logging.php | 8 + .../ProcessMaker/TaskScheduler/TaskTest.php | 141 +++++++++++++ workflow/engine/bin/cron.php | 51 ++--- workflow/engine/bin/cron_single.php | 150 +++++--------- .../src/ProcessMaker/TaskScheduler/Task.php | 185 ++++++++++++++++++ 6 files changed, 427 insertions(+), 122 deletions(-) create mode 100644 app/Jobs/TaskScheduler.php create mode 100644 tests/unit/workflow/engine/src/ProcessMaker/TaskScheduler/TaskTest.php create mode 100644 workflow/engine/src/ProcessMaker/TaskScheduler/Task.php diff --git a/app/Jobs/TaskScheduler.php b/app/Jobs/TaskScheduler.php new file mode 100644 index 000000000..f36c86c2f --- /dev/null +++ b/app/Jobs/TaskScheduler.php @@ -0,0 +1,14 @@ + 'debug', 'days' => $app->make('config')->get('app.log_max_files', 60), ], + + 'taskScheduler' => [ + 'driver' => 'daily', + 'tap' => [App\Logging\CustomizeFormatter::class], + 'path' => storage_path('logs/taskScheduler.log'), + 'level' => 'debug', + 'days' => $app->make('config')->get('app.log_max_files', 60), + ], 'slack' => [ 'driver' => 'slack', diff --git a/tests/unit/workflow/engine/src/ProcessMaker/TaskScheduler/TaskTest.php b/tests/unit/workflow/engine/src/ProcessMaker/TaskScheduler/TaskTest.php new file mode 100644 index 000000000..e31862018 --- /dev/null +++ b/tests/unit/workflow/engine/src/ProcessMaker/TaskScheduler/TaskTest.php @@ -0,0 +1,141 @@ +faker = Factory::create(); + } + + /** + * Method tearDown. + */ + protected function tearDown() + { + parent::tearDown(); + } + + /** + * Test synchronous asynchronous cases. + */ + public function asynchronousCases() + { + return [ + [true], + [false] + ]; + } + + /** + * This test verify the setExecutionMessage method. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::setExecutionMessage() + * @dataProvider asynchronousCases + */ + public function it_should_test_setExecutionMessage_method($asynchronous) + { + $task = new Task($asynchronous, ''); + $message = $this->faker->paragraph; + + ob_start(); + $task->setExecutionMessage($message); + $printing = ob_get_clean(); + + //assert if message is contained in output buffer + $this->assertRegExp("/{$message}/", $printing); + } + + /** + * This test verify the setExecutionResultMessage method. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::setExecutionResultMessage() + * @dataProvider asynchronousCases + */ + public function it_should_test_setExecutionResultMessage_method($asynchronous) + { + $task = new Task($asynchronous, ''); + $message = $this->faker->paragraph; + + ob_start(); + $task->setExecutionResultMessage($message, 'error'); + $printing = ob_get_clean(); + //assert if message is contained in output buffer + $this->assertRegExp("/{$message}/", $printing); + + ob_start(); + $task->setExecutionResultMessage($message, 'info'); + $printing = ob_get_clean(); + //assert if message is contained in output buffer + $this->assertRegExp("/{$message}/", $printing); + + ob_start(); + $task->setExecutionResultMessage($message, 'warning'); + $printing = ob_get_clean(); + //assert if message is contained in output buffer + $this->assertRegExp("/{$message}/", $printing); + } + + /** + * This test verify the saveLog method. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::saveLog() + * @dataProvider asynchronousCases + */ + public function it_should_test_saveLog_method($asynchronous) + { + $task = new Task($asynchronous, ''); + $description = $this->faker->paragraph; + + $task->saveLog('', '', $description); + $file = PATH_DATA . "log/cron.log"; + $this->assertFileExists($file); + $contentLog = file_get_contents($file); + $this->assertRegExp("/{$description}/", $contentLog); + } + + /** + * This test verify the resendEmails activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::resendEmails() + * @dataProvider asynchronousCases + */ + public function it_should_test_resendEmails_method($asynchronous) + { + $task = new Task($asynchronous, ''); + $dateSystem = $this->faker->date(); + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->resendEmails('', $dateSystem); + $printing = ob_get_clean(); + $this->assertRegExp("/DONE/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->resendEmails('', $dateSystem); + Queue::assertPushed(TaskScheduler::class); + } + } +} diff --git a/workflow/engine/bin/cron.php b/workflow/engine/bin/cron.php index 7b552d923..4e33ef73a 100644 --- a/workflow/engine/bin/cron.php +++ b/workflow/engine/bin/cron.php @@ -13,15 +13,15 @@ try { $osIsLinux = strtoupper(substr(PHP_OS, 0, 3)) != 'WIN'; $arrayCronConfig = [ - 'cron' => ['title' => 'CRON'], - 'ldapcron' => ['title' => 'LDAP Advanced CRON'], + 'cron' => ['title' => 'CRON'], + 'ldapcron' => ['title' => 'LDAP Advanced CRON'], 'messageeventcron' => ['title' => 'Message-Event CRON'], - 'timereventcron' => ['title' => 'Timer-Event CRON'], + 'timereventcron' => ['title' => 'Timer-Event CRON'], 'sendnotificationscron' => ['title' => 'Send-Notifications CRON'] ]; //Define constants - define('PATH_SEP', ($osIsLinux)? '/' : '\\'); + define('PATH_SEP', ($osIsLinux) ? '/' : '\\'); $arrayPathToCron = []; $flagPathToCron = false; @@ -51,8 +51,8 @@ try { $pathOutTrunk = implode(PATH_SEP, $arrayPathToCron) . PATH_SEP; - define('PATH_HOME', $pathHome); - define('PATH_TRUNK', $pathTrunk); + define('PATH_HOME', $pathHome); + define('PATH_TRUNK', $pathTrunk); define('PATH_OUTTRUNK', $pathOutTrunk); //Check deprecated files @@ -86,20 +86,20 @@ try { $arraySystemConfiguration = System::getSystemConfiguration(); - $e_all = (defined('E_DEPRECATED'))? E_ALL & ~E_DEPRECATED : E_ALL; - $e_all = (defined('E_STRICT'))? $e_all & ~E_STRICT : $e_all; - $e_all = ($arraySystemConfiguration['debug'])? $e_all : $e_all & ~E_NOTICE; + $e_all = (defined('E_DEPRECATED')) ? E_ALL & ~E_DEPRECATED : E_ALL; + $e_all = (defined('E_STRICT')) ? $e_all & ~E_STRICT : $e_all; + $e_all = ($arraySystemConfiguration['debug']) ? $e_all : $e_all & ~E_NOTICE; //In community version the default value is 0 - $systemUtcTimeZone = (int)($arraySystemConfiguration['system_utc_time_zone']) == 1; + $systemUtcTimeZone = (int) ($arraySystemConfiguration['system_utc_time_zone']) == 1; app()->useStoragePath(realpath(PATH_DATA)); app()->make(Kernel::class)->bootstrap(); restore_error_handler(); //Do not change any of these settings directly, use env.ini instead - ini_set('display_errors', $arraySystemConfiguration['debug']); + ini_set('display_errors', $arraySystemConfiguration['debug']); ini_set('error_reporting', $e_all); - ini_set('short_open_tag', 'On'); + ini_set('short_open_tag', 'On'); ini_set('default_charset', 'UTF-8'); ini_set('soap.wsdl_cache_enabled', $arraySystemConfiguration['wsdl_cache']); ini_set('date.timezone', $systemUtcTimeZone ? 'UTC' : $arraySystemConfiguration['time_zone']); @@ -128,7 +128,8 @@ try { if (in_array($arrayCommandOption['force'], $argv)) { unset($argv[array_search($arrayCommandOption['force'], $argv)]); - + //reindex keys + $argv = array_values($argv); $force = true; } @@ -137,8 +138,8 @@ try { //Get data of CRON file $arrayCron = unserialize(trim(file_get_contents(PATH_DATA . $cronName))); - $flagIsRunning = (bool)((isset($arrayCron['flagIsRunning']))? $arrayCron['flagIsRunning'] : $arrayCron['bCronIsRunning']); - $lastExecution = (isset($arrayCron['lastExecution']))? $arrayCron['lastExecution'] : $arrayCron['sLastExecution']; + $flagIsRunning = (bool) ((isset($arrayCron['flagIsRunning'])) ? $arrayCron['flagIsRunning'] : $arrayCron['bCronIsRunning']); + $lastExecution = (isset($arrayCron['lastExecution'])) ? $arrayCron['lastExecution'] : $arrayCron['sLastExecution']; } if (!$force && $osIsLinux) { @@ -168,9 +169,9 @@ try { try { $cronSinglePath = PATH_CORE . 'bin' . PATH_SEP . 'cron_single.php'; - $workspace = ''; - $date = ''; - $argvx = ''; + $workspace = ''; + $date = ''; + $argvx = ''; for ($i = 1; $i <= count($argv) - 1; $i++) { if (!isset($argv[$i])) { @@ -189,7 +190,7 @@ try { } if (!$flagDate) { - $argvx = $argvx . (($argvx != '')? ' ' : '') . $argv[$i]; + $argvx = $argvx . (($argvx != '') ? ' ' : '') . $argv[$i]; } } } @@ -208,8 +209,8 @@ try { if (is_dir(PATH_DB . $entry)) { if (file_exists(PATH_DB . $entry . PATH_SEP . 'db.php')) { $counterw++; - - passthru('php -f "' . $cronSinglePath . '" "' . base64_encode(PATH_HOME) . '" "' . base64_encode(PATH_TRUNK) . '" "' . base64_encode(PATH_OUTTRUNK) . '" ' . $cronName . ' ' . $entry . ' "' . $date . '" ' . $argvx); + $command = 'php -f "' . $cronSinglePath . '" "' . base64_encode(PATH_HOME) . '" "' . base64_encode(PATH_TRUNK) . '" "' . base64_encode(PATH_OUTTRUNK) . '" ' . $cronName . ' ' . $entry . ' "' . $date . '" ' . $argvx; + passthru($command); } } } @@ -220,8 +221,8 @@ try { } $counterw++; - - passthru('php -f "' . $cronSinglePath . '" "' . base64_encode(PATH_HOME) . '" "' . base64_encode(PATH_TRUNK) . '" "' . base64_encode(PATH_OUTTRUNK) . '" ' . $cronName . ' ' . $workspace . ' "' . $date . '" ' . $argvx); + $command = 'php -f "' . $cronSinglePath . '" "' . base64_encode(PATH_HOME) . '" "' . base64_encode(PATH_TRUNK) . '" "' . base64_encode(PATH_OUTTRUNK) . '" ' . $cronName . ' ' . $workspace . ' "' . $date . '" ' . $argvx; + passthru($command); } eprintln('Finished ' . $counterw . ' workspaces processed'); @@ -234,13 +235,13 @@ try { file_put_contents(PATH_DATA . $cronName, serialize($arrayCron)); } else { eprintln('The ' . $arrayCronConfig[$cronName]['title'] . ' is running, please wait for it to finish' . "\n" . 'Started in ' . $lastExecution); - eprintln('If do you want force the execution use the option "' . $arrayCommandOption['force'] . '", example: php -f ' . $cronName . '.php +wworkflow ' . $arrayCommandOption['force'] ,'green'); + eprintln('If do you want force the execution use the option "' . $arrayCommandOption['force'] . '", example: php -f ' . $cronName . '.php +wworkflow ' . $arrayCommandOption['force'], 'green'); } echo 'Done!' . "\n"; } catch (Exception $e) { $token = strtotime("now"); PMException::registerErrorLog($e, $token); - G::outRes( G::LoadTranslation("ID_EXCEPTION_LOG_INTERFAZ", array($token)) . "\n" ); + G::outRes(G::LoadTranslation("ID_EXCEPTION_LOG_INTERFAZ", [$token]) . "\n"); } diff --git a/workflow/engine/bin/cron_single.php b/workflow/engine/bin/cron_single.php index 83c5fa260..c64b6bdc1 100644 --- a/workflow/engine/bin/cron_single.php +++ b/workflow/engine/bin/cron_single.php @@ -25,6 +25,7 @@ require_once __DIR__ . '/../../../bootstrap/app.php'; use ProcessMaker\Core\JobsManager; use ProcessMaker\Core\System; use ProcessMaker\Plugins\PluginRegistry; +use ProcessMaker\TaskScheduler\Task; register_shutdown_function(function () { if (class_exists("Propel")) { @@ -55,6 +56,14 @@ try { $cronName = $argv[4]; $workspace = $argv[5]; $now = $argv[6]; //date + //asynchronous flag + $asynchronous = false; + $result = array_search('+async', $argv); + if ($result !== false && is_int($result)) { + $asynchronous = true; + unset($argv[$result]); + $argv = array_values($argv); + } //Defines constants define('PATH_SEP', ($osIsLinux) ? '/' : '\\'); @@ -280,7 +289,31 @@ try { try { switch ($cronName) { case 'cron': - processWorkspace(); + try { + $task = new Task($asynchronous, $sObject); + if (empty($argvx) || strpos($argvx, "emails") !== false) { + $task->resendEmails($now, $dateSystem); + } + unpauseApplications(); + calculateDuration(); + /*----------------------------------********---------------------------------*/ + calculateAppDuration(); + /*----------------------------------********---------------------------------*/ + executeEvents(); + executeScheduledCases(); + executeUpdateAppTitle(); + executeCaseSelfService(); + cleanSelfServiceTables(); + executePlugins(); + /*----------------------------------********---------------------------------*/ + fillReportByUser(); + fillReportByProcess(); + synchronizeDrive(); + synchronizeGmailLabels(); + /*----------------------------------********---------------------------------*/ + } catch (Exception $oError) { + saveLog("main", "error", "Error processing workspace : " . $oError->getMessage() . "\n"); + } break; case 'ldapcron': require_once(PATH_HOME . 'engine' . PATH_SEP . 'methods' . PATH_SEP . 'services' . PATH_SEP . 'ldapadvanced.php'); @@ -328,101 +361,6 @@ try { G::outRes(G::LoadTranslation("ID_EXCEPTION_LOG_INTERFAZ", array($token)) . "\n"); } -//Functions -function processWorkspace() -{ - try { - global $sObject; - global $sLastExecution; - - resendEmails(); - unpauseApplications(); - calculateDuration(); - /*----------------------------------********---------------------------------*/ - calculateAppDuration(); - /*----------------------------------********---------------------------------*/ - executeEvents($sLastExecution); - executeScheduledCases(); - executeUpdateAppTitle(); - executeCaseSelfService(); - cleanSelfServiceTables(); - executePlugins(); - /*----------------------------------********---------------------------------*/ - fillReportByUser(); - fillReportByProcess(); - synchronizeDrive(); - synchronizeGmailLabels(); - /*----------------------------------********---------------------------------*/ - } catch (Exception $oError) { - saveLog("main", "error", "Error processing workspace : " . $oError->getMessage() . "\n"); - } -} - -function resendEmails() -{ - global $argvx; - global $now; - global $dateSystem; - - if ($argvx != "" && strpos($argvx, "emails") === false) { - return false; - } - - setExecutionMessage("Resending emails"); - - try { - $dateResend = $now; - - if ($now == $dateSystem) { - $arrayDateSystem = getdate(strtotime($dateSystem)); - - $mktDateSystem = mktime( - $arrayDateSystem["hours"], - $arrayDateSystem["minutes"], - $arrayDateSystem["seconds"], - $arrayDateSystem["mon"], - $arrayDateSystem["mday"], - $arrayDateSystem["year"] - ); - - $dateResend = date("Y-m-d H:i:s", $mktDateSystem - (7 * 24 * 60 * 60)); - } - - $oSpool = new SpoolRun(); - $oSpool->resendEmails($dateResend, 1); - - saveLog("resendEmails", "action", "Resending Emails", "c"); - - $aSpoolWarnings = $oSpool->getWarnings(); - - if ($aSpoolWarnings !== false) { - foreach ($aSpoolWarnings as $sWarning) { - print("MAIL SPOOL WARNING: " . $sWarning . "\n"); - saveLog("resendEmails", "warning", "MAIL SPOOL WARNING: " . $sWarning); - } - } - - setExecutionResultMessage("DONE"); - } catch (Exception $e) { - $c = new Criteria("workflow"); - $c->clearSelectColumns(); - $c->addSelectColumn(ConfigurationPeer::CFG_UID); - $c->add(ConfigurationPeer::CFG_UID, "Emails"); - $result = ConfigurationPeer::doSelectRS($c); - $result->setFetchmode(ResultSet::FETCHMODE_ASSOC); - if ($result->next()) { - setExecutionResultMessage("WARNING", "warning"); - $message = "Emails won't be sent, but the cron will continue its execution"; - eprintln(" '-" . $message, "yellow"); - } else { - setExecutionResultMessage("WITH ERRORS", "error"); - eprintln(" '-" . $e->getMessage(), "red"); - } - - saveLog("resendEmails", "error", "Error Resending Emails: " . $e->getMessage()); - } -} - function unpauseApplications() { global $argvx; @@ -570,8 +508,9 @@ function calculateAppDuration() } /*----------------------------------********---------------------------------*/ -function executeEvents($sLastExecution, $now = null) +function executeEvents() { + global $sLastExecution; global $argvx; global $now; @@ -859,6 +798,14 @@ function executeCaseSelfService() } } +/** + * @deprecated This function is only used in this file and must be deleted. + * @global string $sObject + * @global string $isDebug + * @param string $sSource + * @param string $sType + * @param string $sDescription + */ function saveLog($sSource, $sType, $sDescription) { try { @@ -876,6 +823,10 @@ function saveLog($sSource, $sType, $sDescription) } } +/** + * @deprecated This function is only used in this file and must be deleted. + * @param string $m + */ function setExecutionMessage($m) { $len = strlen($m); @@ -889,6 +840,11 @@ function setExecutionMessage($m) } } +/** + * @deprecated This function is only used in this file and must be deleted. + * @param string $m + * @param string $t + */ function setExecutionResultMessage($m, $t = '') { $c = 'green'; diff --git a/workflow/engine/src/ProcessMaker/TaskScheduler/Task.php b/workflow/engine/src/ProcessMaker/TaskScheduler/Task.php new file mode 100644 index 000000000..d781f34ee --- /dev/null +++ b/workflow/engine/src/ProcessMaker/TaskScheduler/Task.php @@ -0,0 +1,185 @@ +asynchronous = $asynchronous; + $this->object = $object; + } + + /** + * Run job, the property async indicate if is synchronous or asynchronous. + * @param callable $job + */ + private function runTask(callable $job) + { + if ($this->asynchronous === false) { + $job(); + } + if ($this->asynchronous === true) { + JobsManager::getSingleton()->dispatch(TaskScheduler::class, $job); + } + } + + /** + * Print start message in console. + * @param string $message + */ + public function setExecutionMessage(string $message) + { + Log::channel('taskScheduler:taskScheduler')->info($message, Bootstrap::context()); + $len = strlen($message); + $linesize = 60; + $rOffset = $linesize - $len; + + eprint("* $message"); + + for ($i = 0; $i < $rOffset; $i++) { + eprint('.'); + } + } + + /** + * Print result message in console. + * @param string $message + * @param string $type + */ + public function setExecutionResultMessage(string $message, string $type = '') + { + $color = 'green'; + if ($type == 'error') { + $color = 'red'; + Log::channel('taskScheduler:taskScheduler')->error($message, Bootstrap::context()); + } + if ($type == 'info') { + $color = 'yellow'; + Log::channel('taskScheduler:taskScheduler')->info($message, Bootstrap::context()); + } + if ($type == 'warning') { + $color = 'yellow'; + Log::channel('taskScheduler:taskScheduler')->warning($message, Bootstrap::context()); + } + eprintln("[$message]", $color); + } + + /** + * Save logs. + * @param string $source + * @param string $type + * @param string $description + */ + public function saveLog(string $source, string $type, string $description) + { + $context = [ + 'type' => $type, + 'description' => $description + ]; + Log::channel('taskScheduler:taskScheduler')->info($source, Bootstrap::context($context)); + try { + G::verifyPath(PATH_DATA . "log" . PATH_SEP, true); + G::log("| $this->object | " . $source . " | $type | " . $description, PATH_DATA); + } catch (Exception $e) { + Log::channel('taskScheduler:taskScheduler')->error($e->getMessage(), Bootstrap::context($context)); + } + } + + /** + * This resend the emails. + * @param string $now + * @param string $dateSystem + */ + public function resendEmails($now, $dateSystem) + { + $job = function() use($now, $dateSystem) { + $this->setExecutionMessage("Resending emails"); + + try { + $dateResend = $now; + + if ($now == $dateSystem) { + $arrayDateSystem = getdate(strtotime($dateSystem)); + + $mktDateSystem = mktime( + $arrayDateSystem["hours"], + $arrayDateSystem["minutes"], + $arrayDateSystem["seconds"], + $arrayDateSystem["mon"], + $arrayDateSystem["mday"], + $arrayDateSystem["year"] + ); + + $dateResend = date("Y-m-d H:i:s", $mktDateSystem - (7 * 24 * 60 * 60)); + } + + $spoolRun = new SpoolRun(); + $spoolRun->resendEmails($dateResend, 1); + + $this->saveLog("resendEmails", "action", "Resending Emails", "c"); + + $spoolWarnings = $spoolRun->getWarnings(); + + if ($spoolWarnings !== false) { + foreach ($spoolWarnings as $warning) { + print("MAIL SPOOL WARNING: " . $warning . "\n"); + $this->saveLog("resendEmails", "warning", "MAIL SPOOL WARNING: " . $warning); + } + } + + $this->setExecutionResultMessage("DONE"); + } catch (Exception $e) { + $context = [ + "trace" => $e->getTraceAsString() + ]; + Log::channel('taskScheduler:taskScheduler')->error($e->getMessage(), Bootstrap::context($context)); + $criteria = new Criteria("workflow"); + $criteria->clearSelectColumns(); + $criteria->addSelectColumn(ConfigurationPeer::CFG_UID); + $criteria->add(ConfigurationPeer::CFG_UID, "Emails"); + $result = ConfigurationPeer::doSelectRS($criteria); + $result->setFetchmode(ResultSet::FETCHMODE_ASSOC); + if ($result->next()) { + $this->setExecutionResultMessage("WARNING", "warning"); + $message = "Emails won't be sent, but the cron will continue its execution"; + eprintln(" '-" . $message, "yellow"); + } else { + $this->setExecutionResultMessage("WITH ERRORS", "error"); + eprintln(" '-" . $e->getMessage(), "red"); + } + + $this->saveLog("resendEmails", "error", "Error Resending Emails: " . $e->getMessage()); + } + }; + $this->runTask($job); + } +}