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/database/factories/AppTimeoutActionFactory.php b/database/factories/AppTimeoutActionFactory.php new file mode 100644 index 000000000..c9a120d07 --- /dev/null +++ b/database/factories/AppTimeoutActionFactory.php @@ -0,0 +1,12 @@ +define(\ProcessMaker\Model\AppTimeoutAction::class, function (Faker $faker) { + $index = $faker->unique()->numberBetween(20); + return [ + 'APP_UID' => G::generateUniqueID(), + 'DEL_INDEX' => $index, + 'EXECUTION_DATE' => $faker->dateTime() + ]; +}); diff --git a/database/factories/TaskFactory.php b/database/factories/TaskFactory.php index 90bbe13a2..d585bd3b9 100644 --- a/database/factories/TaskFactory.php +++ b/database/factories/TaskFactory.php @@ -29,10 +29,11 @@ $factory->define(\ProcessMaker\Model\Task::class, function(Faker $faker) { 'TAS_OWNER_APP' => 'FALSE', 'TAS_CAN_SEND_MESSAGE' => 'FALSE', 'TAS_SEND_LAST_EMAIL' => 'FALSE', + 'TAS_SELFSERVICE_TIMEOUT' => 0, ]; }); -// Create a delegation with the foreign keys +// Create a task with the foreign keys $factory->state(\ProcessMaker\Model\Task::class, 'foreign_keys', function (Faker $faker) { $process = factory(\ProcessMaker\Model\Process::class)->create(); return [ @@ -57,5 +58,29 @@ $factory->state(\ProcessMaker\Model\Task::class, 'foreign_keys', function (Faker 'TAS_OWNER_APP' => 'FALSE', 'TAS_CAN_SEND_MESSAGE' => 'FALSE', 'TAS_SEND_LAST_EMAIL' => 'FALSE', + 'TAS_SELFSERVICE_TIMEOUT' => 0, + ]; +}); + +// Create a task related with the self-service timeout execution +$factory->state(\ProcessMaker\Model\Task::class, 'sef_service_timeout', function (Faker $faker) { + $timeUnit = $faker->randomElement(['MINUTES', 'HOURS', 'DAYS']); + $execution = $faker->randomElement(['EVERY_TIME', 'ONCE']); + return [ + 'TAS_UID' => G::generateUniqueID(), + 'TAS_ID' => $faker->unique()->numberBetween(1, 200000), + 'TAS_TITLE' => $faker->sentence(2), + 'TAS_TYPE' => 'NORMAL', + 'TAS_TYPE_DAY' => 1, + 'TAS_DURATION' => 1, + 'TAS_ASSIGN_TYPE' => 'SELF_SERVICE', + 'TAS_ASSIGN_VARIABLE' => '@@SYS_NEXT_USER_TO_BE_ASSIGNED', + 'TAS_SELFSERVICE_TIMEOUT' => 1, + 'TAS_SELFSERVICE_TIME' => $faker->unique()->numberBetween(1, 24), + 'TAS_SELFSERVICE_TIME_UNIT' => $timeUnit, + 'TAS_SELFSERVICE_TRIGGER_UID' => function() { + return $trigger = factory(\ProcessMaker\Model\Triggers::class)->create()->TRI_UID; + }, + 'TAS_SELFSERVICE_EXECUTION' => $execution, ]; }); diff --git a/database/factories/TriggerFactory.php b/database/factories/TriggersFactory.php similarity index 92% rename from database/factories/TriggerFactory.php rename to database/factories/TriggersFactory.php index f467c626a..11117f083 100644 --- a/database/factories/TriggerFactory.php +++ b/database/factories/TriggersFactory.php @@ -12,7 +12,7 @@ $factory->define(Triggers::class, function (Faker $faker) { return factory(\ProcessMaker\Model\Process::class)->create()->PRO_UID; }, 'TRI_TYPE' => 'SCRIPT', - 'TRI_WEBBOT' => $faker->text, + 'TRI_WEBBOT' => '$var = 1;', 'TRI_PARAM' => '', ]; }); diff --git a/gulliver/system/class.g.php b/gulliver/system/class.g.php index de2d836e6..658466d62 100644 --- a/gulliver/system/class.g.php +++ b/gulliver/system/class.g.php @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Request; use ProcessMaker\Core\System; +use ProcessMaker\Log\AuditLog; use ProcessMaker\Plugins\PluginRegistry; use ProcessMaker\Services\OAuth2\Server; use ProcessMaker\Validation\ValidationUploadedFiles; diff --git a/tests/unit/workflow/engine/classes/WorkflowToolsTest.php b/tests/unit/workflow/engine/classes/WorkflowToolsTest.php new file mode 100644 index 000000000..e863b2cbe --- /dev/null +++ b/tests/unit/workflow/engine/classes/WorkflowToolsTest.php @@ -0,0 +1,50 @@ +workspaceTools = new WorkspaceTools('workflow'); + } + + /** + * Method tear down. + */ + public function tearDown() + { + parent::tearDown(); + } + + /** + * This test the addAsyncOptionToSchedulerCommands method. + * @test + * @covers \WorkspaceTools::addAsyncOptionToSchedulerCommands() + */ + public function it_should_test_addAsyncOptionToSchedulerCommands_method() + { + //method "WorkspaceTools::initPropel(true)" crashes all connections + $message = "WorkspaceTools::initPropel(true) crashes all connections"; + $this->markTestIncomplete($message); + + ob_start(); + $this->workspaceTools->addAsyncOptionToSchedulerCommands(false); + $string = ob_get_clean(); + $this->assertRegExp("/This was previously updated/", $string); + + ob_start(); + $this->workspaceTools->addAsyncOptionToSchedulerCommands(true); + $string = ob_get_clean(); + $this->assertRegExp("/Adding \+async option/", $string); + } +} diff --git a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php index 55c762947..88c9294a9 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php @@ -4,10 +4,15 @@ namespace ProcessMaker\BusinessModel; use Exception; use G; +use Illuminate\Foundation\Testing\DatabaseTransactions; +use Illuminate\Support\Facades\DB; use ProcessMaker\BusinessModel\Cases; use ProcessMaker\Model\Application; use ProcessMaker\Model\Delegation; use ProcessMaker\Model\Documents; +use ProcessMaker\Model\ListUnassigned; +use ProcessMaker\Model\Task; +use ProcessMaker\Model\Triggers; use ProcessMaker\Model\User; use RBAC; use Tests\TestCase; @@ -19,7 +24,6 @@ use Tests\TestCase; */ class CasesTest extends TestCase { - /** * Set up method. */ @@ -233,4 +237,82 @@ class CasesTest extends TestCase // Call the uploadFiles method $case->uploadFiles($user->USR_UID, $application->APP_UID, $varName, -1, null, $delegation->DEL_INDEX); } + + /** + * This test the execution of trigger from cases related to the self services timeout + * + * @covers \ProcessMaker\BusinessModel\Cases::executeSelfServiceTimeout() + * @test + */ + public function it_execute_trigger_from_cases_with_self_service_timeout_every_time() + { + ListUnassigned::truncate(); + // Define the Execute Trigger = EVERY_TIME + $application = factory(Application::class)->states('foreign_keys')->create(); + // Create a trigger + $trigger = factory(Triggers::class)->create([ + 'PRO_UID' => $application->PRO_UID, + 'TRI_WEBBOT' => 'echo(1);' + ]); + // Create a task with the configuration trigger execution + $task = factory(Task::class)->states('sef_service_timeout')->create([ + 'PRO_UID' => $application->PRO_UID, + 'TAS_SELFSERVICE_EXECUTION' => 'EVERY_TIME', + 'TAS_SELFSERVICE_TRIGGER_UID' => $trigger->TRI_UID + ]); + // Create a unassigned cases + factory(ListUnassigned::class)->create([ + 'TAS_UID' => $task->TAS_UID, + 'TAS_ID' => $task->TAS_ID, + 'APP_NUMBER' => $application->APP_NUMBER, + 'APP_UID' => $application->APP_UID, + 'PRO_UID' => $application->PRO_UID + ]); + // Define the session + $_SESSION["PROCESS"] = $application->PRO_UID; + + // todo: the function Cases::loadCase is using propel we need to change this + DB::commit(); + $casesExecuted = Cases::executeSelfServiceTimeout(); + $this->assertTrue(is_array($casesExecuted)); + } + + /** + * This test the execution of trigger from cases related to the self services timeout + * + * @covers \ProcessMaker\BusinessModel\Cases::executeSelfServiceTimeout() + * @test + */ + public function it_execute_trigger_from_cases_with_self_service_timeout_once() + { + ListUnassigned::truncate(); + // Define the Execute Trigger = ONCE + $application = factory(Application::class)->states('foreign_keys')->create(); + // Create a trigger + $trigger = factory(Triggers::class)->create([ + 'PRO_UID' => $application->PRO_UID, + 'TRI_WEBBOT' => 'echo(1);' + ]); + // Create a task with the configuration trigger execution + $task = factory(Task::class)->states('sef_service_timeout')->create([ + 'PRO_UID' => $application->PRO_UID, + 'TAS_SELFSERVICE_EXECUTION' => 'ONCE', + 'TAS_SELFSERVICE_TRIGGER_UID' => $trigger->TRI_UID + ]); + // Create a unassigned cases + factory(ListUnassigned::class)->create([ + 'TAS_UID' => $task->TAS_UID, + 'TAS_ID' => $task->TAS_ID, + 'APP_NUMBER' => $application->APP_NUMBER, + 'APP_UID' => $application->APP_UID, + 'PRO_UID' => $application->PRO_UID + ]); + // Define the session + $_SESSION["PROCESS"] = $application->PRO_UID; + + // todo: the function Cases::loadCase is using propel we need to change this + DB::commit(); + $casesExecuted = Cases::executeSelfServiceTimeout(); + $this->assertTrue(is_array($casesExecuted)); + } } diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Model/AppTimeoutActionTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Model/AppTimeoutActionTest.php new file mode 100644 index 000000000..eafdfefec --- /dev/null +++ b/tests/unit/workflow/engine/src/ProcessMaker/Model/AppTimeoutActionTest.php @@ -0,0 +1,120 @@ +create(); + $timeout = factory(AppTimeoutAction::class)->create(); + $timeout->setCaseUid($timeout->APP_UID); + $this->assertEquals($timeout->getCaseUid(), $timeout->APP_UID); + } + + /** + * Test set and get the index property + * + * @covers \ProcessMaker\Model\AppTimeoutAction::setIndex() + * @covers \ProcessMaker\Model\AppTimeoutAction::getIndex() + * @test + */ + public function it_set_get_index() + { + factory(AppTimeoutAction::class)->create(); + $timeout = factory(AppTimeoutAction::class)->create(); + $timeout->setIndex($timeout->DEL_INDEX); + $this->assertEquals($timeout->getIndex(), $timeout->DEL_INDEX); + } + + /** + * Test a query to only include a specific case + * + * @covers \ProcessMaker\Model\AppTimeoutAction::scopeCase() + * @test + */ + public function it_filter_a_specific_case() + { + factory(AppTimeoutAction::class)->create(); + $timeout = factory(AppTimeoutAction::class)->create(); + $this->assertCount(1, $timeout->case($timeout->APP_UID)->get()); + } + + /** + * Test scope a query to only include a specific case + * + * @covers \ProcessMaker\Model\AppTimeoutAction::scopeIndex() + * @test + */ + public function it_filter_a_specific_index() + { + factory(AppTimeoutAction::class)->create(); + $timeout = factory(AppTimeoutAction::class)->create(); + $this->assertCount(1, $timeout->case($timeout->APP_UID)->index($timeout->DEL_INDEX)->get()); + } + + /** + * This checks it returns information about the self service timeout in a sequential thread + * + * @covers \ProcessMaker\Model\AppTimeoutAction::cases() + * @test + */ + public function it_return_the_case_executed_once_one_thread() + { + $records = factory(AppTimeoutAction::class, 5)->create(); + foreach ($records as $row) { + $appUid = $row->APP_UID; + $delIndex = $row->DEL_INDEX; + } + + $appTimeout = new AppTimeoutAction(); + $appTimeout->setCaseUid($appUid); + $appTimeout->setIndex($delIndex); + $caseExecuted = $appTimeout->cases(); + $this->assertNotEmpty($caseExecuted); + } + + /** + * This checks it returns information about the self service timeout in a parallel thread + * + * @covers \ProcessMaker\Model\AppTimeoutAction::cases() + * @test + */ + public function it_return_the_case_executed_once_more_than_one_thread() + { + $records = factory(AppTimeoutAction::class, 5)->create(); + foreach ($records as $row) { + $appUid = $row->APP_UID; + $delIndex = $row->DEL_INDEX; + } + // Create other thread in the same case + factory(AppTimeoutAction::class)->create([ + 'APP_UID' => $appUid, + 'DEL_INDEX' => $delIndex + 1, + ]); + + $appTimeout = new AppTimeoutAction(); + $appTimeout->setCaseUid($appUid); + $appTimeout->setIndex($delIndex); + $caseExecuted = $appTimeout->cases(); + $this->assertNotEmpty($caseExecuted); + } +} diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Model/ListUnassignedTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Model/ListUnassignedTest.php index 9b8e3a4c6..d58bf69fe 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Model/ListUnassignedTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Model/ListUnassignedTest.php @@ -300,5 +300,25 @@ class ListUnassignedTest extends TestCase $result = ListUnassigned::loadList($user->USR_UID, $filters); $this->assertCount(2, $result); } + + /** + * This checks the self-service timeout cases + * + * @covers \ProcessMaker\Model\ListUnassigned::selfServiceTimeout() + * @test + */ + public function it_should_return_cases_configured_self_service_timeout() + { + // Create some cases configured the self service timeout + for ($x = 1; $x <= 5; $x++) { + $task = factory(Task::class)->states('sef_service_timeout')->create(); + factory(ListUnassigned::class)->create([ + 'TAS_UID' => $task->TAS_UID, + 'TAS_ID' => $task->TAS_ID + ]); + } + $results = ListUnassigned::selfServiceTimeout(); + $this->assertCount(5, $results); + } } diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Model/TriggersTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Model/TriggersTest.php index 36b4239d9..ef411aa24 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Model/TriggersTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Model/TriggersTest.php @@ -2,14 +2,40 @@ namespace Tests\unit\workflow\engine\src\ProcessMaker\Model; +use G; +use Illuminate\Foundation\Testing\DatabaseTransactions; use ProcessMaker\Model\Process; use ProcessMaker\Model\Triggers; use Tests\TestCase; +/** + * Class DelegationTest + * + * @coversDefaultClass \ProcessMaker\Model\Triggers + */ class TriggersTest extends TestCase { + use DatabaseTransactions; + + /** + * Test set and get the trigger property + * + * @covers \ProcessMaker\Model\Triggers::setTrigger() + * @covers \ProcessMaker\Model\Triggers::getTrigger() + * @test + */ + public function it_set_get_trigger() + { + factory(Triggers::class)->create(); + $trigger = factory(Triggers::class)->create(); + $trigger->setTrigger($trigger->TRI_UID); + $this->assertEquals($trigger->getTrigger(), $trigger->TRI_UID); + } + /** * It tests the process scope in the trigger model + * + * @covers \ProcessMaker\Model\Triggers::scopeProcess() * @test */ public function it_should_test_process_scope_in_trigger_model() @@ -74,4 +100,48 @@ class TriggersTest extends TestCase $this->assertEquals($process[2]['PRO_UID'], $result[0]['PRO_UID']); $this->assertEquals($process[2]['PRO_UID'], $result[1]['PRO_UID']); } -} \ No newline at end of file + + /** + * Test scope a query to only include a specific case + * + * @covers \ProcessMaker\Model\Triggers::scopeTrigger() + * @test + */ + public function it_filter_specific_tasks() + { + factory(Triggers::class)->create(); + $trigger = factory(Triggers::class)->create(); + $this->assertCount(1, $trigger->trigger($trigger->TRI_UID)->get()); + } + + /** + * This checks it returns information about the trigger + * + * @covers \ProcessMaker\Model\Triggers::triggers() + * @test + */ + public function it_return_specific_trigger_information() + { + $triggers = factory(Triggers::class, 5)->create(); + $trigger = new Triggers(); + $trigger->setTrigger($triggers[0]->TRI_UID); + $triggersList = $trigger->triggers(); + $this->assertCount(1, $triggersList); + $this->assertEquals($triggers[0]->TRI_TITLE , $triggersList[0]['TRI_TITLE']); + } + + /** + * This checks it returns empty when the trigger does not exist + * + * @covers \ProcessMaker\Model\Triggers::triggers() + * @test + */ + public function it_return_empty_when_the_trigger_not_exist() + { + factory(Triggers::class)->create(); + $trigger = new Triggers(); + $trigger->setTrigger(G::generateUniqueID()); + $triggersList = $trigger->triggers(); + $this->assertEmpty($triggersList); + } +} 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..46ca031eb --- /dev/null +++ b/tests/unit/workflow/engine/src/ProcessMaker/TaskScheduler/TaskTest.php @@ -0,0 +1,560 @@ +faker = Factory::create(); + Delegation::truncate(); + AppThread::truncate(); + Application::truncate(); + } + + /** + * 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 + if ($asynchronous === false) { + $this->assertRegExp("/{$message}/", $printing); + } + //assert if not showing message + if ($asynchronous === true) { + $this->assertEmpty($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 + if ($asynchronous === false) { + $this->assertRegExp("/{$message}/", $printing); + } + //assert if not showing message + if ($asynchronous === true) { + $this->assertEmpty($printing); + } + + ob_start(); + $task->setExecutionResultMessage($message, 'info'); + $printing = ob_get_clean(); + //assert if message is contained in output buffer + if ($asynchronous === false) { + $this->assertRegExp("/{$message}/", $printing); + } + //assert if not showing message + if ($asynchronous === true) { + $this->assertEmpty($printing); + } + + ob_start(); + $task->setExecutionResultMessage($message, 'warning'); + $printing = ob_get_clean(); + //assert if message is contained in output buffer + if ($asynchronous === false) { + $this->assertRegExp("/{$message}/", $printing); + } + //assert if not showing message + if ($asynchronous === true) { + $this->assertEmpty($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(false, ''); + $task->saveLog('', '', $this->faker->paragraph); + $file = PATH_DATA . "log/cron.log"; + $this->assertFileExists($file); + + if ($asynchronous === false) { + $description = $this->faker->paragraph; + $task = new Task($asynchronous, ''); + $task->saveLog('', '', $description); + $contentLog = file_get_contents($file); + + $this->assertRegExp("/{$description}/", $contentLog); + } + if ($asynchronous === true) { + $description = $this->faker->paragraph; + $task = new Task($asynchronous, ''); + $task->saveLog('', '', $description); + $contentLog = file_get_contents($file); + + $this->assertNotRegExp("/{$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); + } + } + + /** + * This test verify the unpauseApplications activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::unpauseApplications() + * @dataProvider asynchronousCases + */ + public function it_should_test_unpauseApplications_method($asynchronous) + { + $task = new Task($asynchronous, ''); + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->unpauseApplications(''); + $printing = ob_get_clean(); + $this->assertRegExp("/DONE/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->unpauseApplications(''); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the calculateDuration activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::calculateDuration() + * @dataProvider asynchronousCases + */ + public function it_should_test_calculateDuration_method($asynchronous) + { + $task = new Task($asynchronous, ''); + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->calculateDuration(); + $printing = ob_get_clean(); + $this->assertRegExp("/DONE/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->calculateDuration(); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the calculateDuration activity method for synchronous and asynchronous execution. + * @covers ProcessMaker\TaskScheduler\Task::executeCaseSelfService() + * @test + * @dataProvider asynchronousCases + */ + public function it_should_test_unassignedcase($asynchronous) + { + $task = new Task($asynchronous, ''); + + // Assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->executeCaseSelfService(); + $printing = ob_get_clean(); + $this->assertRegExp("/Unassigned case/", $printing); + } + + // Assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->executeCaseSelfService(); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the calculateAppDuration activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::calculateAppDuration() + * @dataProvider asynchronousCases + */ + public function it_should_test_calculateAppDuration_method($asynchronous) + { + $task = new Task($asynchronous, ''); + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->calculateAppDuration(); + $printing = ob_get_clean(); + $this->assertRegExp("/DONE/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->calculateAppDuration(); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the cleanSelfServiceTables activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::cleanSelfServiceTables() + * @dataProvider asynchronousCases + */ + public function it_should_test_cleanSelfServiceTables_method($asynchronous) + { + $task = new Task($asynchronous, ''); + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->cleanSelfServiceTables(); + $printing = ob_get_clean(); + $this->assertRegExp("/DONE/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->cleanSelfServiceTables(); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the executePlugins activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::executePlugins() + * @dataProvider asynchronousCases + */ + public function it_should_test_executePlugins_method($asynchronous) + { + $task = new Task($asynchronous, ''); + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->executePlugins(); + $printing = ob_get_clean(); + $this->assertRegExp("/plugins/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->executePlugins(); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the fillReportByUser activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::fillReportByUser() + * @dataProvider asynchronousCases + */ + public function it_should_test_fillReportByUser_method($asynchronous) + { + $task = new Task($asynchronous, ''); + $dateInit = $this->faker->dateTime; + $dateFinish = $this->faker->dateTime; + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->fillReportByUser($dateInit, $dateFinish); + $printing = ob_get_clean(); + $this->assertRegExp("/User Reporting/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->fillReportByUser($dateInit, $dateFinish); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the fillReportByProcess activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::fillReportByProcess() + * @dataProvider asynchronousCases + */ + public function it_should_test_fillReportByProcess_method($asynchronous) + { + $task = new Task($asynchronous, ''); + $dateInit = $this->faker->dateTime; + $dateFinish = $this->faker->dateTime; + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->fillReportByProcess($dateInit, $dateFinish); + $printing = ob_get_clean(); + $this->assertRegExp("/Process Reporting/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->fillReportByProcess($dateInit, $dateFinish); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the ldapcron activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::ldapcron() + * @dataProvider asynchronousCases + */ + public function it_should_test_ldapcron_method($asynchronous) + { + $task = new Task($asynchronous, ''); + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->ldapcron(false); + $printing = ob_get_clean(); + $this->assertRegExp("/\+---/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->ldapcron(false); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the sendNotifications activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::sendNotifications() + * @dataProvider asynchronousCases + */ + public function it_should_test_sendNotifications_method($asynchronous) + { + $task = new Task($asynchronous, ''); + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->sendNotifications(); + $printing = ob_get_clean(); + $this->assertRegExp("/Resending Notifications/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->sendNotifications(); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the actionsByEmailResponse activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::actionsByEmailResponse() + * @dataProvider asynchronousCases + */ + public function it_should_test_actionsByEmailResponse_method($asynchronous) + { + $task = new Task($asynchronous, ''); + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->actionsByEmailResponse(); + $printing = ob_get_clean(); + $this->assertEmpty($printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->actionsByEmailResponse(); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * This test verify the messageeventcron activity method for synchronous and asynchronous execution. + * @test + * @covers ProcessMaker\TaskScheduler\Task::runTask() + * @covers ProcessMaker\TaskScheduler\Task::messageeventcron() + * @dataProvider asynchronousCases + */ + public function it_should_test_messageeventcron_method($asynchronous) + { + $task = new Task($asynchronous, ''); + + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + $task->messageeventcron(); + $printing = ob_get_clean(); + $this->assertRegExp("/Message-Events/", $printing); + } + + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->messageeventcron(); + Queue::assertPushed(TaskScheduler::class); + } + } + + /** + * Tests the timerEventCron method with jobs in the task scheduler + * + * @test + * @covers ProcessMaker\TaskScheduler\Task::timerEventCron() + * @dataProvider asynchronousCases + */ + public function it_should_test_the_timer_event_cron_method($asynchronous) + { + //Creates a new task + $task = new Task($asynchronous, ''); + //Sets the currect date + $date = date('Y-m-d H:i:s'); + //assert synchronous for cron file + if ($asynchronous === false) { + ob_start(); + //Calls the timerEventCron method + $task->timerEventCron($date, true); + //Gets the result + $printing = ob_get_clean(); + //Asserts the result is printing that there is no exisiting records to continue a case in the determined date + $this->assertRegExp('/No existing records to continue a case, on date "' . $date . '/', $printing); + } + //assert asynchronous for job process + if ($asynchronous === true) { + Queue::fake(); + Queue::assertNothingPushed(); + $task->timerEventCron($date, true); + Queue::assertPushed(TaskScheduler::class); + } + } +} diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/CalculateDateTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/CalculateDateTest.php new file mode 100644 index 000000000..cf5a14803 --- /dev/null +++ b/tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/CalculateDateTest.php @@ -0,0 +1,44 @@ +assertEquals('2019-10-05 12:07:40', $newDate); + } + + /** + * It tests the addition of hours + * + * @test + */ + public function it_test_adding_hours() + { + $iniDate = '2019-10-04 12:07:40'; + $newDate = calculateDate($iniDate, 'HOURS', 1); + $this->assertEquals('2019-10-04 13:07:40', $newDate); + } + + /** + * It tests the addition of minutes + * + * @test + */ + public function it_test_adding_minutes() + { + $iniDate = '2019-10-04 12:07:40'; + $newDate = calculateDate($iniDate, 'MINUTES', 10); + $this->assertEquals('2019-10-04 12:17:40', $newDate); + } +} diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/ChangeAbbreviationOfDirectives.php b/tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/ChangeAbbreviationOfDirectivesTest.php similarity index 94% rename from tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/ChangeAbbreviationOfDirectives.php rename to tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/ChangeAbbreviationOfDirectivesTest.php index 4c36b2fa2..fd975b42d 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/ChangeAbbreviationOfDirectives.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/ChangeAbbreviationOfDirectivesTest.php @@ -4,7 +4,7 @@ namespace Tests\unit\workflow\src\ProcessMaker\Util\Helpers; use Tests\TestCase; -class ChangeAbbreviationOfDirectives extends TestCase +class ChangeAbbreviationOfDirectivesTest extends TestCase { /** * Provider to define different types of configurations in the php.ini and the result expected diff --git a/workflow/engine/bin/cron.php b/workflow/engine/bin/cron.php index 71e2cfb6d..98c382ead 100644 --- a/workflow/engine/bin/cron.php +++ b/workflow/engine/bin/cron.php @@ -13,16 +13,16 @@ 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'], 'sendnotificationscron' => ['title' => 'Send-Notifications CRON'], - 'webentriescron' => ['title' => 'Wen Entries CRON'] + 'webentriescron' => ['title' => 'Web Entries CRON'] ]; //Define constants - define('PATH_SEP', ($osIsLinux)? '/' : '\\'); + define('PATH_SEP', ($osIsLinux) ? '/' : '\\'); $arrayPathToCron = []; $flagPathToCron = false; @@ -52,8 +52,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 @@ -87,20 +87,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']); @@ -129,7 +129,8 @@ try { if (in_array($arrayCommandOption['force'], $argv)) { unset($argv[array_search($arrayCommandOption['force'], $argv)]); - + //reindex keys + $argv = array_values($argv); $force = true; } @@ -138,8 +139,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) { @@ -169,9 +170,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])) { @@ -190,7 +191,7 @@ try { } if (!$flagDate) { - $argvx = $argvx . (($argvx != '')? ' ' : '') . $argv[$i]; + $argvx = $argvx . (($argvx != '') ? ' ' : '') . $argv[$i]; } } } @@ -209,8 +210,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); } } } @@ -221,8 +222,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'); @@ -235,13 +236,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 f1c7fe678..1459215b4 100644 --- a/workflow/engine/bin/cron_single.php +++ b/workflow/engine/bin/cron_single.php @@ -4,7 +4,6 @@ * cron_single.php * * @see workflow/engine/bin/cron.php - * @see workflow/engine/bin/messageeventcron.php * @see workflow/engine/bin/timereventcron.php * @see workflow/engine/bin/ldapcron.php * @see workflow/engine/bin/sendnotificationscron.php @@ -15,9 +14,7 @@ */ use Illuminate\Foundation\Http\Kernel; -/*----------------------------------********---------------------------------*/ -use ProcessMaker\BusinessModel\ActionsByEmail\ResponseReader; -/*----------------------------------********---------------------------------*/ +use ProcessMaker\BusinessModel\Cases; require_once __DIR__ . '/../../../gulliver/system/class.g.php'; require_once __DIR__ . '/../../../bootstrap/autoload.php'; @@ -27,6 +24,7 @@ use ProcessMaker\BusinessModel\WebEntry; use ProcessMaker\Core\JobsManager; use ProcessMaker\Core\System; use ProcessMaker\Plugins\PluginRegistry; +use ProcessMaker\TaskScheduler\Task; register_shutdown_function(function () { if (class_exists("Propel")) { @@ -57,6 +55,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) ? '/' : '\\'); @@ -282,27 +288,65 @@ try { try { switch ($cronName) { case 'cron': - processWorkspace(); + try { + $task = new Task($asynchronous, $sObject); + if (empty($argvx) || strpos($argvx, "emails") !== false) { + $task->resendEmails($now, $dateSystem); + } + if (empty($argvx) || strpos($argvx, "unpause") !== false) { + $task->unpauseApplications($now); + } + if (empty($argvx) || strpos($argvx, "calculate") !== false) { + $task->calculateDuration(); + } + /*----------------------------------********---------------------------------*/ + if (empty($argvx) || strpos($argvx, "calculateapp") !== false) { + $task->calculateAppDuration(); + } + /*----------------------------------********---------------------------------*/ + executeEvents(); + executeScheduledCases(); + executeUpdateAppTitle(); + if (empty($argvx) || strpos($argvx, "unassigned-case") !== false) { + $task->executeCaseSelfService(); + } + if (empty($argvx) || strpos($argvx, "clean-self-service-tables") !== false) { + $task->cleanSelfServiceTables(); + } + if (empty($argvx) || strpos($argvx, "plugins") !== false) { + $task->executePlugins(); + } + /*----------------------------------********---------------------------------*/ + if (strpos($argvx, "report_by_user") !== false) { + $task->fillReportByUser($dateInit, $dateFinish); + } + if (strpos($argvx, "report_by_process") !== false) { + $task->fillReportByProcess($dateInit, $dateFinish); + } + 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'); - - $ldapadvancedClassCron = new ldapadvancedClassCron(); - - $ldapadvancedClassCron->executeCron(in_array('+debug', $argv)); + $task = new Task($asynchronous, $sObject); + $task->ldapcron(in_array('+debug', $argv)); break; case 'messageeventcron': - $messageApplication = new \ProcessMaker\BusinessModel\MessageApplication(); - - $messageApplication->catchMessageEvent(true); + $task = new Task($asynchronous, $sObject); + $task->messageeventcron(); break; case 'timereventcron': - $timerEvent = new \ProcessMaker\BusinessModel\TimerEvent(); - - $timerEvent->startContinueCaseByTimerEvent($now, true); + $task = new Task($asynchronous, $sObject); + $task->timerEventCron($now, true); break; case 'sendnotificationscron': - sendNotifications(); + if (empty($argvx) || strpos($argvx, "send-notifications") !== false) { + $task = new Task($asynchronous, $sObject); + $task->sendNotifications(); + } break; case 'webentriescron': setExecutionMessage('Deleting web entry cases created one week ago or more'); @@ -311,7 +355,8 @@ try { break; /*----------------------------------********---------------------------------*/ case 'actionsByEmailEmailResponse': - (new ResponseReader)->actionsByEmailEmailResponse(); + $task = new Task($asynchronous, $sObject); + $task->actionsByEmailResponse(); break; /*----------------------------------********---------------------------------*/ } @@ -335,250 +380,9 @@ 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; - global $now; - - if ($argvx != "" && strpos($argvx, "unpause") === false) { - return false; - } - - setExecutionMessage("Unpausing applications"); - - try { - $oCases = new Cases(); - $oCases->ThrowUnpauseDaemon($now, 1); - - setExecutionResultMessage('DONE'); - saveLog('unpauseApplications', 'action', 'Unpausing Applications'); - } catch (Exception $oError) { - setExecutionResultMessage('WITH ERRORS', 'error'); - eprintln(" '-" . $oError->getMessage(), 'red'); - saveLog('unpauseApplications', 'error', 'Error Unpausing Applications: ' . $oError->getMessage()); - } -} - -function executePlugins() -{ - global $argvx; - - if ($argvx != "" && strpos($argvx, "plugins") === false) { - return false; - } - - $pathCronPlugins = PATH_CORE . 'bin' . PATH_SEP . 'plugins' . PATH_SEP; - - // Executing cron files in bin/plugins directory - if (!is_dir($pathCronPlugins)) { - return false; - } - - if ($handle = opendir($pathCronPlugins)) { - setExecutionMessage('Executing cron files in bin/plugins directory in Workspace: ' . config("system.workspace")); - while (false !== ($file = readdir($handle))) { - if (strpos($file, '.php', 1) && is_file($pathCronPlugins . $file)) { - $filename = str_replace('.php', '', $file); - $className = $filename . 'ClassCron'; - - // Execute custom cron function - executeCustomCronFunction($pathCronPlugins . $file, $className); - } - } - } - - // Executing registered cron files - // -> Get registered cron files - $oPluginRegistry = PluginRegistry::loadSingleton(); - $cronFiles = $oPluginRegistry->getCronFiles(); - - // -> Execute functions - if (!empty($cronFiles)) { - setExecutionMessage('Executing registered cron files for Workspace: ' . config('system.workspace')); - /** - * @var \ProcessMaker\Plugins\Interfaces\CronFile $cronFile - */ - foreach ($cronFiles as $cronFile) { - $path = PATH_PLUGINS . $cronFile->getNamespace() . PATH_SEP . 'bin' . PATH_SEP . $cronFile->getCronFile() . '.php'; - if (file_exists($path)) { - executeCustomCronFunction($path, $cronFile->getCronFile()); - } else { - setExecutionMessage('File ' . $cronFile->getCronFile() . '.php ' . 'does not exist.'); - } - } - } -} - -function executeCustomCronFunction($pathFile, $className) -{ - include_once $pathFile; - - $oPlugin = new $className(); - - if (method_exists($oPlugin, 'executeCron')) { - $arrayCron = unserialize(trim(@file_get_contents(PATH_DATA . "cron"))); - $arrayCron["processcTimeProcess"] = 60; //Minutes - $arrayCron["processcTimeStart"] = time(); - @file_put_contents(PATH_DATA . "cron", serialize($arrayCron)); - - //Try to execute Plugin Cron. If there is an error then continue with the next file - setExecutionMessage("\n--- Executing cron file: $pathFile"); - try { - $oPlugin->executeCron(); - setExecutionResultMessage('DONE'); - } catch (Exception $e) { - setExecutionResultMessage('FAILED', 'error'); - eprintln(" '-" . $e->getMessage(), 'red'); - saveLog('executePlugins', 'error', 'Error executing cron file: ' . $pathFile . ' - ' . $e->getMessage()); - } - } -} - -function calculateDuration() -{ - global $argvx; - - if ($argvx != "" && strpos($argvx, "calculate") === false) { - return false; - } - - setExecutionMessage("Calculating Duration"); - - try { - $oAppDelegation = new AppDelegation(); - $oAppDelegation->calculateDuration(1); - - setExecutionResultMessage('DONE'); - saveLog('calculateDuration', 'action', 'Calculating Duration'); - } catch (Exception $oError) { - setExecutionResultMessage('WITH ERRORS', 'error'); - eprintln(" '-" . $oError->getMessage(), 'red'); - saveLog('calculateDuration', 'error', 'Error Calculating Duration: ' . $oError->getMessage()); - } -} -/*----------------------------------********---------------------------------*/ - -function calculateAppDuration() -{ - global $argvx; - - if ($argvx != "" && strpos($argvx, "calculateapp") === false) { - return false; - } - - setExecutionMessage("Calculating Duration by Application"); - - try { - $oApplication = new Application(); - $oApplication->calculateAppDuration(1); - - setExecutionResultMessage('DONE'); - saveLog('calculateDurationByApp', 'action', 'Calculating Duration by Application'); - } catch (Exception $oError) { - setExecutionResultMessage('WITH ERRORS', 'error'); - eprintln(" '-" . $oError->getMessage(), 'red'); - saveLog('calculateDurationByApp', 'error', 'Error Calculating Duration: ' . $oError->getMessage()); - } -} -/*----------------------------------********---------------------------------*/ - -function executeEvents($sLastExecution, $now = null) +function executeEvents() { + global $sLastExecution; global $argvx; global $now; @@ -696,176 +500,14 @@ function executeUpdateAppTitle() } } -function executeCaseSelfService() -{ - try { - global $argvx; - - if ($argvx != "" && strpos($argvx, "unassigned-case") === false) { - return false; - } - - $criteria = new Criteria("workflow"); - - //SELECT - $criteria->addSelectColumn(AppCacheViewPeer::APP_UID); - $criteria->addSelectColumn(AppCacheViewPeer::DEL_INDEX); - $criteria->addSelectColumn(AppCacheViewPeer::DEL_DELEGATE_DATE); - $criteria->addSelectColumn(AppCacheViewPeer::APP_NUMBER); - $criteria->addSelectColumn(AppCacheViewPeer::PRO_UID); - $criteria->addSelectColumn(TaskPeer::TAS_UID); - $criteria->addSelectColumn(TaskPeer::TAS_SELFSERVICE_TIME); - $criteria->addSelectColumn(TaskPeer::TAS_SELFSERVICE_TIME_UNIT); - $criteria->addSelectColumn(TaskPeer::TAS_SELFSERVICE_TRIGGER_UID); - /*----------------------------------********---------------------------------*/ - $criteria->addSelectColumn(TaskPeer::TAS_SELFSERVICE_EXECUTION); - /*----------------------------------********---------------------------------*/ - - //FROM - $condition = array(); - $condition[] = array(AppCacheViewPeer::TAS_UID, TaskPeer::TAS_UID); - $condition[] = array(TaskPeer::TAS_SELFSERVICE_TIMEOUT, 1); - $criteria->addJoinMC($condition, Criteria::LEFT_JOIN); - - //WHERE - $criteria->add(AppCacheViewPeer::USR_UID, ""); - $criteria->add(AppCacheViewPeer::DEL_THREAD_STATUS, "OPEN"); - - //QUERY - $rsCriteria = AppCacheViewPeer::doSelectRS($criteria); - $rsCriteria->setFetchmode(ResultSet::FETCHMODE_ASSOC); - - setExecutionMessage("Unassigned case"); - saveLog("unassignedCase", "action", "Unassigned case", "c"); - - $calendar = new Calendar(); - - while ($rsCriteria->next()) { - $row = $rsCriteria->getRow(); - $flag = false; - - $appcacheAppUid = $row["APP_UID"]; - $appcacheDelIndex = $row["DEL_INDEX"]; - $appcacheDelDelegateDate = $row["DEL_DELEGATE_DATE"]; - $appcacheAppNumber = $row["APP_NUMBER"]; - $appcacheProUid = $row["PRO_UID"]; - $taskUid = $row["TAS_UID"]; - $taskSelfServiceTime = intval($row["TAS_SELFSERVICE_TIME"]); - $taskSelfServiceTimeUnit = $row["TAS_SELFSERVICE_TIME_UNIT"]; - $taskSelfServiceTriggerUid = $row["TAS_SELFSERVICE_TRIGGER_UID"]; - /*----------------------------------********---------------------------------*/ - $taskSelfServiceJustOneExecution = $row["TAS_SELFSERVICE_EXECUTION"]; - - if ($taskSelfServiceJustOneExecution == 'ONCE') { - $criteriaSelfService = new Criteria("workflow"); - - $criteriaSelfService->add(AppTimeoutActionExecutedPeer::APP_UID, $appcacheAppUid); - $criteriaSelfService->add(AppTimeoutActionExecutedPeer::DEL_INDEX, $appcacheDelIndex); - - $querySelfService = AppTimeoutActionExecutedPeer::doSelectRS($criteriaSelfService); - $querySelfService->setFetchmode(ResultSet::FETCHMODE_ASSOC); - - if ($querySelfService->next()) { - $row = $querySelfService->getRow(); - $flag = true; //already executed - } - } - /*----------------------------------********---------------------------------*/ - - if ($calendar->pmCalendarUid == '') { - $calendar->getCalendar(null, $appcacheProUid, $taskUid); - $calendar->getCalendarData(); - } - - $dueDate = $calendar->calculateDate( - $appcacheDelDelegateDate, - $taskSelfServiceTime, - $taskSelfServiceTimeUnit //HOURS|DAYS|MINUTES - //1 - ); - - if (time() > $dueDate["DUE_DATE_SECONDS"] && $flag == false) { - $sessProcess = null; - $sessProcessSw = 0; - - //Load data - $case = new Cases(); - $appFields = $case->loadCase($appcacheAppUid); - - $appFields["APP_DATA"]["APPLICATION"] = $appcacheAppUid; - - if (isset($_SESSION["PROCESS"])) { - $sessProcess = $_SESSION["PROCESS"]; - $sessProcessSw = 1; - } - - $_SESSION["PROCESS"] = $appFields["PRO_UID"]; - - //Execute trigger - $criteriaTgr = new Criteria(); - $criteriaTgr->add(TriggersPeer::TRI_UID, $taskSelfServiceTriggerUid); - - $rsCriteriaTgr = TriggersPeer::doSelectRS($criteriaTgr); - $rsCriteriaTgr->setFetchmode(ResultSet::FETCHMODE_ASSOC); - - if ($rsCriteriaTgr->next()) { - $row = $rsCriteriaTgr->getRow(); - - if (is_array($row) && $row["TRI_TYPE"] == "SCRIPT") { - $arrayCron = unserialize(trim(@file_get_contents(PATH_DATA . "cron"))); - $arrayCron["processcTimeProcess"] = 60; //Minutes - $arrayCron["processcTimeStart"] = time(); - @file_put_contents(PATH_DATA . "cron", serialize($arrayCron)); - - //Trigger - global $oPMScript; - - $oPMScript = new PMScript(); - $oPMScript->setDataTrigger($row); - $oPMScript->setFields($appFields["APP_DATA"]); - $oPMScript->setScript($row["TRI_WEBBOT"]); - $oPMScript->setExecutedOn(PMScript::SELF_SERVICE_TIMEOUT); - $oPMScript->execute(); - - /*----------------------------------********---------------------------------*/ - //saving the case`s data if the 'Execution' is set in ONCE. - if ($taskSelfServiceJustOneExecution == "ONCE") { - $oAppTimeoutActionExecuted = new AppTimeoutActionExecuted(); - $dataSelf = array(); - $dataSelf["APP_UID"] = $appcacheAppUid; - $dataSelf["DEL_INDEX"] = $appcacheDelIndex; - $dataSelf["EXECUTION_DATE"] = time(); - $oAppTimeoutActionExecuted->create($dataSelf); - } - /*----------------------------------********---------------------------------*/ - $appFields["APP_DATA"] = array_merge($appFields["APP_DATA"], $oPMScript->aFields); - - unset($appFields['APP_STATUS']); - unset($appFields['APP_PROC_STATUS']); - unset($appFields['APP_PROC_CODE']); - unset($appFields['APP_PIN']); - $case->updateCase($appFields["APP_UID"], $appFields); - - saveLog("unassignedCase", "action", "OK Executed trigger to the case $appcacheAppNumber"); - } - } - - unset($_SESSION["PROCESS"]); - - if ($sessProcessSw == 1) { - $_SESSION["PROCESS"] = $sessProcess; - } - } - } - - setExecutionResultMessage("DONE"); - } catch (Exception $e) { - setExecutionResultMessage("WITH ERRORS", "error"); - eprintln(" '-" . $e->getMessage(), "red"); - saveLog("unassignedCase", "error", "Error in unassigned case: " . $e->getMessage()); - } -} - +/** + * @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 { @@ -883,6 +525,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); @@ -896,6 +542,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'; @@ -916,67 +567,6 @@ function setExecutionResultMessage($m, $t = '') } /*----------------------------------********---------------------------------*/ -function fillReportByUser() -{ - try { - global $argvx; - global $dateInit; - global $dateFinish; - - if (strpos($argvx, "report_by_user") === false) { - return false; - } - if ($dateInit == null) { - eprintln("You must enter the starting date.", "red"); - eprintln('Example: +init-date"YYYY-MM-DD HH:MM:SS" +finish-date"YYYY-MM-DD HH:MM:SS"', "red"); - return false; - } - - $dateFinish = ($dateFinish != null) ? $dateFinish : date("Y-m-d H:i:s"); - - $appcv = new AppCacheView(); - $appcv->setPathToAppCacheFiles(PATH_METHODS . 'setup' . PATH_SEP . 'setupSchemas' . PATH_SEP); - setExecutionMessage("Calculating data to fill the 'User Reporting'..."); - $appcv->fillReportByUser($dateInit, $dateFinish); - setExecutionResultMessage("DONE"); - } catch (Exception $e) { - setExecutionResultMessage("WITH ERRORS", "error"); - eprintln(" '-" . $e->getMessage(), "red"); - saveLog("fillReportByUser", "error", "Error in fill report by user: " . $e->getMessage()); - } -} - -function fillReportByProcess() -{ - try { - global $argvx; - global $dateInit; - global $dateFinish; - - if (strpos($argvx, "report_by_process") === false) { - return false; - } - - if ($dateInit == null) { - eprintln("You must enter the starting date.", "red"); - eprintln('Example: +init-date"YYYY-MM-DD HH:MM:SS" +finish-date"YYYY-MM-DD HH:MM:SS"', "red"); - return false; - } - - $dateFinish = ($dateFinish != null) ? $dateFinish : date("Y-m-d H:i:s"); - $appcv = new AppCacheView(); - $appcv->setPathToAppCacheFiles(PATH_METHODS . 'setup' . PATH_SEP . 'setupSchemas' . PATH_SEP); - - setExecutionMessage("Calculating data to fill the 'Process Reporting'..."); - $appcv->fillReportByProcess($dateInit, $dateFinish); - setExecutionResultMessage("DONE"); - } catch (Exception $e) { - setExecutionResultMessage("WITH ERRORS", "error"); - eprintln(" '-" . $e->getMessage(), "red"); - saveLog("fillReportByProcess", "error", "Error in fill report by process: " . $e->getMessage()); - } -} - function synchronizeDrive() { try { @@ -1034,94 +624,3 @@ function synchronizeGmailLabels() } } /*----------------------------------********---------------------------------*/ - -function sendNotifications() -{ - try { - global $argvx; - if ($argvx != "" && strpos($argvx, "send-notifications") === false) { - return false; - } - setExecutionMessage("Resending Notifications"); - setExecutionResultMessage("PROCESSING"); - $notQueue = new \NotificationQueue(); - $notQueue->checkIfCasesOpenForResendingNotification(); - $notificationsAndroid = $notQueue->loadStatusDeviceType('pending', 'android'); - if ($notificationsAndroid) { - setExecutionMessage("|-- Send Android's Notifications"); - $n = 0; - foreach ($notificationsAndroid as $key => $item) { - $oNotification = new \ProcessMaker\BusinessModel\Light\PushMessageAndroid(); - $oNotification->setSettingNotification(); - $oNotification->setDevices(unserialize($item['DEV_UID'])); - $response['android'] = $oNotification->send($item['NOT_MSG'], unserialize($item['NOT_DATA'])); - $notQueue = new \NotificationQueue(); - $notQueue->changeStatusSent($item['NOT_UID']); - $n += $oNotification->getNumberDevices(); - } - setExecutionResultMessage("Processed $n"); - } - $notificationsApple = $notQueue->loadStatusDeviceType('pending', 'apple'); - if ($notificationsApple) { - setExecutionMessage("|-- Send Apple Notifications"); - $n = 0; - foreach ($notificationsApple as $key => $item) { - $oNotification = new \ProcessMaker\BusinessModel\Light\PushMessageIOS(); - $oNotification->setSettingNotification(); - $oNotification->setDevices(unserialize($item['DEV_UID'])); - $response['apple'] = $oNotification->send($item['NOT_MSG'], unserialize($item['NOT_DATA'])); - $notQueue = new \NotificationQueue(); - $notQueue->changeStatusSent($item['NOT_UID']); - $n += $oNotification->getNumberDevices(); - } - setExecutionResultMessage("Processed $n"); - } - } catch (Exception $e) { - setExecutionResultMessage("WITH ERRORS", "error"); - eprintln(" '-" . $e->getMessage(), "red"); - saveLog("ExecuteSendNotifications", "error", "Error when sending notifications " . $e->getMessage()); - } -} - -/** - * Clean unused records in tables related to the Self-Service Value Based feature - * - * @see processWorkspace() - * - * @link https://wiki.processmaker.com/3.2/Executing_cron.php#Syntax_of_cron.php_Options - */ -function cleanSelfServiceTables() -{ - try { - global $argvx; - - // Check if the action can be executed - if ($argvx !== "" && strpos($argvx, "clean-self-service-tables") === false) { - return false; - } - - // Start message - setExecutionMessage("Clean unused records for Self-Service Value Based feature"); - - // Get Propel connection - $cnn = Propel::getConnection(AppAssignSelfServiceValueGroupPeer::DATABASE_NAME); - - // Delete related rows and missing relations, criteria don't execute delete with joins - $cnn->begin(); - $stmt = $cnn->createStatement(); - $stmt->executeQuery("DELETE " . AppAssignSelfServiceValueGroupPeer::TABLE_NAME . " - FROM " . AppAssignSelfServiceValueGroupPeer::TABLE_NAME . " - LEFT JOIN " . AppAssignSelfServiceValuePeer::TABLE_NAME . " - ON (" . AppAssignSelfServiceValueGroupPeer::ID . " = " . AppAssignSelfServiceValuePeer::ID . ") - WHERE " . AppAssignSelfServiceValuePeer::ID . " IS NULL"); - $cnn->commit(); - - // Success message - setExecutionResultMessage("DONE"); - } catch (Exception $e) { - $cnn->rollback(); - setExecutionResultMessage("WITH ERRORS", "error"); - eprintln(" '-" . $e->getMessage(), "red"); - saveLog("ExecuteCleanSelfServiceTables", "error", "Error when try to clean self-service tables " . $e->getMessage()); - } -} diff --git a/workflow/engine/bin/tasks/cliWorkspaces.php b/workflow/engine/bin/tasks/cliWorkspaces.php index 46637a332..35a2380a2 100644 --- a/workflow/engine/bin/tasks/cliWorkspaces.php +++ b/workflow/engine/bin/tasks/cliWorkspaces.php @@ -429,6 +429,17 @@ EOT CLI::taskArg('fontFileName', false); CLI::taskRun('documents_remove_font'); +/** + * Add +async option to scheduler commands in table SCHEDULER. + */ +CLI::taskName('add-async-option-to-scheduler-commands'); +CLI::taskDescription(<< Adding +async option to scheduler commands...\n"); + $start = microtime(true); + $workspaceTools->addAsyncOptionToSchedulerCommands(true); + CLI::logging("<*> Adding +async option to scheduler commands took " . (microtime(true) - $start) . " seconds.\n"); + } else { + $workspaces = get_workspaces_from_args($args); + foreach ($workspaces as $workspace) { + passthru(PHP_BINARY . ' processmaker add-async-option-to-scheduler-commands ' . $workspace->name); + } + } +} + /** * Convert Web Entries v1.0 to v2.0 for BPMN processes in order to deprecate the old version. * diff --git a/workflow/engine/classes/WorkspaceTools.php b/workflow/engine/classes/WorkspaceTools.php index 3d4940213..d0d2045d1 100644 --- a/workflow/engine/classes/WorkspaceTools.php +++ b/workflow/engine/classes/WorkspaceTools.php @@ -365,6 +365,11 @@ class WorkspaceTools $start = microtime(true); $this->updateTriggers(true, $lang); CLI::logging("* End updating MySQL triggers...(" . (microtime(true) - $start) . " seconds)\n"); + + CLI::logging("* Start adding +async option to scheduler commands...\n"); + $start = microtime(true); + $this->addAsyncOptionToSchedulerCommands(true); + CLI::logging("* End adding +async option to scheduler commands...(Completed on " . (microtime(true) - $start) . " seconds)\n"); CLI::logging("* Start Converting Web Entries v1.0 to v2.0 for BPMN processes...\n"); $start = microtime(true); @@ -2218,6 +2223,11 @@ class WorkspaceTools $workspace->updateTriggers(true, $lang); CLI::logging("* End updating MySQL triggers...(" . (microtime(true) - $start) . " seconds)\n"); + CLI::logging("* Start adding +async option to scheduler commands...\n"); + $start = microtime(true); + $workspace->addAsyncOptionToSchedulerCommands(false); + CLI::logging("* End adding +async option to scheduler commands...(Completed on " . (microtime(true) - $start) . " seconds)\n"); + CLI::logging("* Start Converting Web Entries v1.0 to v2.0 for BPMN processes...\n"); $start = microtime(true); Bootstrap::setConstantsRelatedWs($workspace); @@ -5084,4 +5094,37 @@ class WorkspaceTools $database = $this->getDatabase($rbac); $database->executeQuery($query, true); } + + /** + * Add +async option to scheduler commands in table SCHEDULER. + * @param boolean $force + */ + public function addAsyncOptionToSchedulerCommands($force = false) + { + //read update status + $this->initPropel(true); + $conf = new Configurations(); + $exist = $conf->exists('ADDED_ASYNC_OPTION_TO_SCHEDULER', 'scheduler'); + if ($exist === true && $force === false) { + $config = (object) $conf->load('ADDED_ASYNC_OPTION_TO_SCHEDULER', 'scheduler'); + if ($config->updated) { + CLI::logging("-> This was previously updated.\n"); + return; + } + } + + //update process + $updateQuery = "" + . "UPDATE {$this->dbName}.SCHEDULER SET body = REPLACE(body, '+force\"', '+force +async\"') " + . "WHERE body NOT LIKE '%+async%'" + . ""; + $con = Propel::getConnection("workflow"); + $stmt = $con->createStatement(); + $stmt->executeQuery($updateQuery); + CLI::logging("-> Adding +async option to scheduler commands in table {$this->dbName}.SCHEDULER\n"); + + //save update status + $conf->aConfig = ['updated' => true]; + $conf->saveConfig('ADDED_ASYNC_OPTION_TO_SCHEDULER', 'scheduler'); + } } diff --git a/workflow/engine/classes/class.pmScript.php b/workflow/engine/classes/class.pmScript.php index 71b081dfa..881fa7453 100644 --- a/workflow/engine/classes/class.pmScript.php +++ b/workflow/engine/classes/class.pmScript.php @@ -302,6 +302,9 @@ class PMScript case 'SCRIPT_TASK': $executedOn = self::SCRIPT_TASK; break; + case 'SELF_SERVICE_TIMEOUT': + $executedOn = self::SELF_SERVICE_TIMEOUT; + break; default: $executedOn = self::UNDEFINED_ORIGIN; break; diff --git a/workflow/engine/classes/model/map/AppTimeoutActionExecutedMapBuilder.php b/workflow/engine/classes/model/map/AppTimeoutActionExecutedMapBuilder.php index 54d8b92d7..6129e543b 100644 --- a/workflow/engine/classes/model/map/AppTimeoutActionExecutedMapBuilder.php +++ b/workflow/engine/classes/model/map/AppTimeoutActionExecutedMapBuilder.php @@ -67,7 +67,7 @@ class AppTimeoutActionExecutedMapBuilder $tMap->addPrimaryKey('APP_UID', 'AppUid', 'string', CreoleTypes::VARCHAR, true, 32); - $tMap->addColumn('DEL_INDEX', 'DelIndex', 'int', CreoleTypes::INTEGER, true, null); + $tMap->addPrimaryKey('DEL_INDEX', 'DelIndex', 'int', CreoleTypes::INTEGER, true, null); $tMap->addColumn('EXECUTION_DATE', 'ExecutionDate', 'int', CreoleTypes::TIMESTAMP, false, null); diff --git a/workflow/engine/classes/model/om/BaseAppTimeoutActionExecuted.php b/workflow/engine/classes/model/om/BaseAppTimeoutActionExecuted.php index 7e5888876..4d49d049b 100644 --- a/workflow/engine/classes/model/om/BaseAppTimeoutActionExecuted.php +++ b/workflow/engine/classes/model/om/BaseAppTimeoutActionExecuted.php @@ -565,28 +565,40 @@ abstract class BaseAppTimeoutActionExecuted extends BaseObject implements Persis $criteria = new Criteria(AppTimeoutActionExecutedPeer::DATABASE_NAME); $criteria->add(AppTimeoutActionExecutedPeer::APP_UID, $this->app_uid); + $criteria->add(AppTimeoutActionExecutedPeer::DEL_INDEX, $this->del_index); return $criteria; } /** - * Returns the primary key for this object (row). - * @return string + * Returns the composite primary key for this object. + * The array elements will be in same order as specified in XML. + * @return array */ public function getPrimaryKey() { - return $this->getAppUid(); + $pks = array(); + + $pks[0] = $this->getAppUid(); + + $pks[1] = $this->getDelIndex(); + + return $pks; } /** - * Generic method to set the primary key (app_uid column). + * Set the [composite] primary key. * - * @param string $key Primary key. + * @param array $keys The elements of the composite key (order must match the order in XML file). * @return void */ - public function setPrimaryKey($key) + public function setPrimaryKey($keys) { - $this->setAppUid($key); + + $this->setAppUid($keys[0]); + + $this->setDelIndex($keys[1]); + } /** @@ -602,8 +614,6 @@ abstract class BaseAppTimeoutActionExecuted extends BaseObject implements Persis public function copyInto($copyObj, $deepCopy = false) { - $copyObj->setDelIndex($this->del_index); - $copyObj->setExecutionDate($this->execution_date); @@ -611,6 +621,8 @@ abstract class BaseAppTimeoutActionExecuted extends BaseObject implements Persis $copyObj->setAppUid(''); // this is a pkey column, so set to default value + $copyObj->setDelIndex('0'); // this is a pkey column, so set to default value + } /** diff --git a/workflow/engine/classes/model/om/BaseAppTimeoutActionExecutedPeer.php b/workflow/engine/classes/model/om/BaseAppTimeoutActionExecutedPeer.php index 7d8cddc9d..83d1f9a43 100644 --- a/workflow/engine/classes/model/om/BaseAppTimeoutActionExecutedPeer.php +++ b/workflow/engine/classes/model/om/BaseAppTimeoutActionExecutedPeer.php @@ -389,6 +389,9 @@ abstract class BaseAppTimeoutActionExecutedPeer $comparison = $criteria->getComparison(AppTimeoutActionExecutedPeer::APP_UID); $selectCriteria->add(AppTimeoutActionExecutedPeer::APP_UID, $criteria->remove(AppTimeoutActionExecutedPeer::APP_UID), $comparison); + $comparison = $criteria->getComparison(AppTimeoutActionExecutedPeer::DEL_INDEX); + $selectCriteria->add(AppTimeoutActionExecutedPeer::DEL_INDEX, $criteria->remove(AppTimeoutActionExecutedPeer::DEL_INDEX), $comparison); + } else { $criteria = $values->buildCriteria(); // gets full criteria $selectCriteria = $values->buildPkeyCriteria(); // gets criteria w/ primary key(s) @@ -450,7 +453,22 @@ abstract class BaseAppTimeoutActionExecutedPeer } else { // it must be the primary key $criteria = new Criteria(self::DATABASE_NAME); - $criteria->add(AppTimeoutActionExecutedPeer::APP_UID, (array) $values, Criteria::IN); + // primary key is composite; we therefore, expect + // the primary key passed to be an array of pkey + // values + if (count($values) == count($values, COUNT_RECURSIVE)) { + // array is not multi-dimensional + $values = array($values); + } + $vals = array(); + foreach ($values as $value) { + + $vals[0][] = $value[0]; + $vals[1][] = $value[1]; + } + + $criteria->add(AppTimeoutActionExecutedPeer::APP_UID, $vals[0], Criteria::IN); + $criteria->add(AppTimeoutActionExecutedPeer::DEL_INDEX, $vals[1], Criteria::IN); } // Set the correct dbName @@ -510,51 +528,23 @@ abstract class BaseAppTimeoutActionExecutedPeer } /** - * Retrieve a single object by pkey. - * - * @param mixed $pk the primary key. - * @param Connection $con the connection to use + * Retrieve object using using composite pkey values. + * @param string $app_uid + * @param int $del_index + * @param Connection $con * @return AppTimeoutActionExecuted */ - public static function retrieveByPK($pk, $con = null) + public static function retrieveByPK($app_uid, $del_index, $con = null) { if ($con === null) { $con = Propel::getConnection(self::DATABASE_NAME); } - - $criteria = new Criteria(AppTimeoutActionExecutedPeer::DATABASE_NAME); - - $criteria->add(AppTimeoutActionExecutedPeer::APP_UID, $pk); - - + $criteria = new Criteria(); + $criteria->add(AppTimeoutActionExecutedPeer::APP_UID, $app_uid); + $criteria->add(AppTimeoutActionExecutedPeer::DEL_INDEX, $del_index); $v = AppTimeoutActionExecutedPeer::doSelect($criteria, $con); - return !empty($v) > 0 ? $v[0] : null; - } - - /** - * Retrieve multiple objects by pkey. - * - * @param array $pks List of primary keys - * @param Connection $con the connection to use - * @throws PropelException Any exceptions caught during processing will be - * rethrown wrapped into a PropelException. - */ - public static function retrieveByPKs($pks, $con = null) - { - if ($con === null) { - $con = Propel::getConnection(self::DATABASE_NAME); - } - - $objs = null; - if (empty($pks)) { - $objs = array(); - } else { - $criteria = new Criteria(); - $criteria->add(AppTimeoutActionExecutedPeer::APP_UID, $pks, Criteria::IN); - $objs = AppTimeoutActionExecutedPeer::doSelect($criteria, $con); - } - return $objs; + return !empty($v) ? $v[0] : null; } } diff --git a/workflow/engine/config/schema.xml b/workflow/engine/config/schema.xml index 1ea687e6f..8a5f5f608 100755 --- a/workflow/engine/config/schema.xml +++ b/workflow/engine/config/schema.xml @@ -4304,7 +4304,7 @@ - +
diff --git a/workflow/engine/data/mysql/schema.sql b/workflow/engine/data/mysql/schema.sql index 83c0a2a60..99625e082 100644 --- a/workflow/engine/data/mysql/schema.sql +++ b/workflow/engine/data/mysql/schema.sql @@ -2314,7 +2314,7 @@ CREATE TABLE `APP_TIMEOUT_ACTION_EXECUTED` `APP_UID` VARCHAR(32) default '' NOT NULL, `DEL_INDEX` INTEGER default 0 NOT NULL, `EXECUTION_DATE` DATETIME, - PRIMARY KEY (`APP_UID`) + PRIMARY KEY (`APP_UID`,`DEL_INDEX`) )ENGINE=InnoDB ; #----------------------------------------------------------------------------- #-- ADDONS_STORE diff --git a/workflow/engine/methods/setup/auditLog.php b/workflow/engine/methods/setup/auditLog.php index e77ec5d57..247e105d9 100644 --- a/workflow/engine/methods/setup/auditLog.php +++ b/workflow/engine/methods/setup/auditLog.php @@ -1,6 +1,6 @@ setUserLogged($_SESSION["USER_LOGGED"]); diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/ActionsByEmail/ResponseReader.php b/workflow/engine/src/ProcessMaker/BusinessModel/ActionsByEmail/ResponseReader.php index 98292c62a..b9db38d34 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/ActionsByEmail/ResponseReader.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/ActionsByEmail/ResponseReader.php @@ -59,7 +59,7 @@ class ResponseReader try { if (!extension_loaded('imap')) { G::outRes(G::LoadTranslation("ID_EXCEPTION_LOG_INTERFAZ", ['php_imap']) . "\n"); - exit; + return; } if (PMLicensedFeatures ::getSingleton() @@ -145,6 +145,7 @@ class ResponseReader $emailSetup['MESS_ACCOUNT'], $this->decryptPassword($emailSetup) ); + Log::channel(':' . $this->channel)->debug("Open mailbox", Bootstrap::context($emailSetup)); // Read all messages into an array $mailsIds = $mailbox->searchMailbox('UNSEEN'); @@ -153,6 +154,7 @@ class ResponseReader foreach ($mailsIds as $key => $mailId) { /** @var IncomingMail $mail */ $mail = $mailbox->getMail($mailId, false); + Log::channel(':' . $this->channel)->debug("Get mail", Bootstrap::context(['mailId' => $mailId])); if (!empty($mail->textPlain)) { preg_match("/{(.*)}/", $mail->textPlain, $matches); if ($matches) { diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php index b41f880f6..c90ec590e 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php @@ -17,6 +17,7 @@ use Applications; use AppNotes; use AppNotesPeer; use AppSolr; +use AppTimeoutActionExecuted; use BasePeer; use Bootstrap; use BpmnEngineServicesSearchIndex; @@ -25,6 +26,7 @@ use CasesPeer; use Configurations; use CreoleTypes; use Criteria; +use DateTime; use DBAdapter; use EntitySolrRequestData; use Exception; @@ -44,8 +46,11 @@ use ProcessMaker\Exception\UploadException; use ProcessMaker\Exception\CaseNoteUploadFile; use ProcessMaker\Model\Application as ModelApplication; use ProcessMaker\Model\AppNotes as Notes; +use ProcessMaker\Model\AppTimeoutAction; use ProcessMaker\Model\Delegation; use ProcessMaker\Model\Documents; +use ProcessMaker\Model\ListUnassigned; +use ProcessMaker\Model\Triggers; use ProcessMaker\Plugins\PluginRegistry; use ProcessMaker\Services\OAuth2\Server; use ProcessMaker\Util\DateTime as UtilDateTime; @@ -4107,4 +4112,127 @@ class Cases return true; } + + /** + * Get the cases related to the self services timeout that needs to execute the trigger related + * + * @return array + * @throws Exception + */ + public static function executeSelfServiceTimeout() + { + try { + $casesSelfService = ListUnassigned::selfServiceTimeout(); + $casesExecuted = []; + foreach ($casesSelfService as $row) { + $appUid = $row["APP_UID"]; + $appNumber = $row["APP_NUMBER"]; + $delIndex = $row["DEL_INDEX"]; + $delegateDate = $row["DEL_DELEGATE_DATE"]; + $proUid = $row["PRO_UID"]; + $taskUid = $row["TAS_UID"]; + $taskSelfServiceTime = intval($row["TAS_SELFSERVICE_TIME"]); + $taskSelfServiceTimeUnit = $row["TAS_SELFSERVICE_TIME_UNIT"]; + $triggerUid = $row["TAS_SELFSERVICE_TRIGGER_UID"]; + + /*----------------------------------********---------------------------------*/ + $typeOfExecution = $row["TAS_SELFSERVICE_EXECUTION"]; + $flagExecuteOnce = true; + // This option will be executed just once, can check if was executed before + if ($typeOfExecution == 'ONCE') { + $appTimeout = new AppTimeoutAction(); + $appTimeout->setCaseUid($appUid); + $appTimeout->setIndex($delIndex); + $caseExecuted = $appTimeout->cases(); + $flagExecuteOnce = !empty($caseExecuted) ? false : true; + } + /*----------------------------------********---------------------------------*/ + + // Add the time in the corresponding unit to the delegation date + $delegateDate = calculateDate($delegateDate, $taskSelfServiceTimeUnit, $taskSelfServiceTime); + + // Define the current time + $datetime = new DateTime('now'); + $currentDate = $datetime->format('Y-m-d H:i:s'); + + // Check if the triggers to be executed + if ($currentDate >= $delegateDate && $flagExecuteOnce) { + // Review if the session process is defined + $sessProcess = null; + $sessProcessSw = false; + if (isset($_SESSION["PROCESS"])) { + $sessProcess = $_SESSION["PROCESS"]; + $sessProcessSw = true; + } + // Load case data + $case = new ClassesCases(); + $appFields = $case->loadCase($appUid); + $appFields["APP_DATA"]["APPLICATION"] = $appUid; + // Set the process defined in the case related + $_SESSION["PROCESS"] = $appFields["PRO_UID"]; + + // Get the trigger related and execute + $triggersList = []; + if (!empty($triggerUid)) { + $trigger = new Triggers(); + $trigger->setTrigger($triggerUid); + $triggersList = $trigger->triggers(); + } + + // If the trigger exist, let's to execute + if (!empty($triggersList)) { + // Execute the trigger defined in the self service timeout + $fieldsCase['APP_DATA'] = $case->executeTriggerFromList( + $triggersList, + $appFields['APP_DATA'], + 'SELF_SERVICE_TIMEOUT', + '', + '', + '', + false + ); + + // Update the case + $case->updateCase($appUid, $fieldsCase); + + /*----------------------------------********---------------------------------*/ + if ($typeOfExecution == 'ONCE') { + // Saving the case`s data if the 'Execution' is set in ONCE. + $appTimeoutActionExecuted = new AppTimeoutActionExecuted(); + $dataSelf = []; + $dataSelf["APP_UID"] = $appUid; + $dataSelf["DEL_INDEX"] = $delIndex; + $dataSelf["EXECUTION_DATE"] = time(); + $appTimeoutActionExecuted->create($dataSelf); + } + /*----------------------------------********---------------------------------*/ + + array_push($casesExecuted, $appNumber); // Register the cases executed + + // Logging this action + $context = [ + 'appUid' => $appUid, + 'appNumber' => $appNumber, + 'triUid' => $triggerUid, + 'proUid' => $proUid, + 'tasUid' => $taskUid, + 'selfServiceTime' => $taskSelfServiceTime, + 'selfServiceTimeUnit' => $taskSelfServiceTimeUnit, + ]; + Log::channel(':TriggerExecution')->info('Timeout trigger execution', Bootstrap::context($context)); + } + + unset($_SESSION["PROCESS"]); + + if ($sessProcessSw) { + $_SESSION["PROCESS"] = $sessProcess; + } + } + } + + return $casesExecuted; + } catch (Exception $e) { + throw $e; + } + } } diff --git a/workflow/engine/src/ProcessMaker/AuditLog/AuditLog.php b/workflow/engine/src/ProcessMaker/Log/AuditLog.php similarity index 99% rename from workflow/engine/src/ProcessMaker/AuditLog/AuditLog.php rename to workflow/engine/src/ProcessMaker/Log/AuditLog.php index c840ff646..f8e6960a8 100644 --- a/workflow/engine/src/ProcessMaker/AuditLog/AuditLog.php +++ b/workflow/engine/src/ProcessMaker/Log/AuditLog.php @@ -1,6 +1,6 @@ caseUid = $caseUid; + } + + /** + * Get Case Uid + * + * @return string + */ + public function getCaseUid() + { + return $this->caseUid; + } + + /** + * Set index + * + * @param int $index + */ + public function setIndex($index) + { + $this->index = $index; + } + + /** + * Get index + * + * @return int + */ + public function getIndex() + { + return $this->index; + } + + /** + * Scope a query to get specific case uid + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $appUid + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeCase($query, $appUid) + { + return $query->where('APP_UID', $appUid); + } + + /** + * Scope a query to get index + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param int $index + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeIndex($query, $index) + { + return $query->where('DEL_INDEX', $index); + } + + /** + * Get the records related to the case and index if it was defined + * + * @return array + */ + public function cases() + { + $query = AppTimeoutAction::query()->select(); + // Specific case uid + if (!empty($this->getCaseUid())) { + $query->case($this->getCaseUid()); + } + // Specific index + if (!empty($this->getIndex())) { + $query->index($this->getIndex()); + } + $results = $query->get()->toArray(); + + return $results; + } +} diff --git a/workflow/engine/src/ProcessMaker/Model/ListUnassigned.php b/workflow/engine/src/ProcessMaker/Model/ListUnassigned.php index ecf6f43c7..3af5059bf 100644 --- a/workflow/engine/src/ProcessMaker/Model/ListUnassigned.php +++ b/workflow/engine/src/ProcessMaker/Model/ListUnassigned.php @@ -46,8 +46,9 @@ class ListUnassigned extends Model /** * Scope a query to only include specific tasks * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param array $tasks + * @param \Illuminate\Database\Eloquent\Builder $query + * @param array $tasks + * * @return \Illuminate\Database\Eloquent\Builder */ public function scopeTasksIn($query, array $tasks) @@ -58,8 +59,8 @@ class ListUnassigned extends Model /** * Scope a query to only include a specific case * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param integer $appNumber + * @param \Illuminate\Database\Eloquent\Builder $query + * @param integer $appNumber * * @return \Illuminate\Database\Eloquent\Builder */ @@ -71,8 +72,8 @@ class ListUnassigned extends Model /** * Scope a query to only include a specific index * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param integer $index + * @param \Illuminate\Database\Eloquent\Builder $query + * @param integer $index * * @return \Illuminate\Database\Eloquent\Builder */ @@ -84,8 +85,8 @@ class ListUnassigned extends Model /** * Scope a query to only include a specific task * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param integer $task + * @param \Illuminate\Database\Eloquent\Builder $query + * @param integer $task * * @return \Illuminate\Database\Eloquent\Builder */ @@ -100,7 +101,7 @@ class ListUnassigned extends Model * @param string $userUid * @param array $filters * - * @return array + * @return int */ public static function doCount($userUid, $filters = []) { @@ -125,4 +126,21 @@ class ListUnassigned extends Model return $result; } + + /** + * Get the unassigned cases related to the self service timeout + * + * @return array + */ + public static function selfServiceTimeout() + { + $query = ListUnassigned::query()->select(); + $query->join('TASK', function ($join) { + $join->on('LIST_UNASSIGNED.TAS_ID', '=', 'TASK.TAS_ID') + ->where('TASK.TAS_SELFSERVICE_TIMEOUT', '=', 1); + }); + $results = $query->get()->toArray(); + + return $results; + } } diff --git a/workflow/engine/src/ProcessMaker/Model/Triggers.php b/workflow/engine/src/ProcessMaker/Model/Triggers.php index 08a25c818..0bc920b27 100644 --- a/workflow/engine/src/ProcessMaker/Model/Triggers.php +++ b/workflow/engine/src/ProcessMaker/Model/Triggers.php @@ -10,11 +10,34 @@ class Triggers extends Model protected $table = 'TRIGGERS'; // No timestamps public $timestamps = false; - //primary key + // Primary key protected $primaryKey = 'TRI_UID'; - //No incrementing + // No incrementing public $incrementing = false; + // Filter by a specific uid + private $triUid = ''; + + /** + * Set trigger uid + * + * @param string $triUid + */ + public function setTrigger($triUid) + { + $this->triUid = $triUid; + } + + /** + * Get trigger uid + * + * @return int + */ + public function getTrigger() + { + return $this->triUid; + } + /** * Scope a query to filter an specific process * @@ -22,8 +45,37 @@ class Triggers extends Model * @param string $columns * @return \Illuminate\Database\Eloquent\Builder */ - public function scopeProcess($query, string $proUID) + public function scopeProcess($query, string $proUid) { - return $query->where('PRO_UID', $proUID); + return $query->where('PRO_UID', $proUid); + } + + /** + * Scope a query to filter an specific trigger + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $triUid + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeTrigger($query, string $triUid) + { + return $query->where('TRI_UID', $triUid); + } + + /** + * Get the records + * + * @return array + */ + public function triggers() + { + $query = Triggers::query()->select(); + // Specific trigger + if (!empty($this->getTrigger())) { + $query->trigger($this->getTrigger()); + } + $results = $query->get()->toArray(); + + return $results; } } \ No newline at end of file diff --git a/workflow/engine/src/ProcessMaker/TaskScheduler/Task.php b/workflow/engine/src/ProcessMaker/TaskScheduler/Task.php new file mode 100644 index 000000000..5a52a014f --- /dev/null +++ b/workflow/engine/src/ProcessMaker/TaskScheduler/Task.php @@ -0,0 +1,687 @@ +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()); + if ($this->asynchronous === false) { + $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()); + } + if ($this->asynchronous === false) { + eprintln("[$message]", $color); + } + } + + /** + * Save logs. + * @param string $source + * @param string $type + * @param string $description + */ + public function saveLog(string $source, string $type, string $description) + { + if ($this->asynchronous === true) { + $context = [ + 'type' => $type, + 'description' => $description + ]; + Log::channel('taskScheduler:taskScheduler')->info($source, Bootstrap::context($context)); + } + if ($this->asynchronous === false) { + 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()); + } + } + } + + /** + * This resend the emails. + * @param string $now + * @param string $dateSystem + */ + public function resendEmails($now, $dateSystem) + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($now, $dateSystem, $scheduledTaskIdentifier) { + $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"; + if ($this->asynchronous === false) { + eprintln(" '-" . $message, "yellow"); + } + } else { + $this->setExecutionResultMessage("WITH ERRORS", "error"); + if ($this->asynchronous === false) { + eprintln(" '-" . $e->getMessage(), "red"); + } + } + + $this->saveLog("resendEmails", "error", "Error Resending Emails: " . $e->getMessage()); + } + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * This unpause applications. + * @param string $now + */ + public function unpauseApplications($now) + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($now, $scheduledTaskIdentifier) { + $this->setExecutionMessage("Unpausing applications"); + try { + $cases = new Cases(); + $cases->ThrowUnpauseDaemon($now, 1); + + $this->setExecutionResultMessage('DONE'); + $this->saveLog('unpauseApplications', 'action', 'Unpausing Applications'); + } catch (Exception $e) { + $this->setExecutionResultMessage('WITH ERRORS', 'error'); + if ($this->asynchronous === false) { + eprintln(" '-" . $e->getMessage(), 'red'); + } + $this->saveLog('unpauseApplications', 'error', 'Error Unpausing Applications: ' . $e->getMessage()); + } + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * Check if some task unassigned has enable the setting timeout and execute the trigger related + * + * @link https://wiki.processmaker.com/3.2/Tasks#Self-Service + */ + function executeCaseSelfService() + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($scheduledTaskIdentifier) { + try { + $this->setExecutionMessage("Unassigned case"); + $this->saveLog("unassignedCase", "action", "Unassigned case", "c"); + $casesExecuted = BmCases::executeSelfServiceTimeout(); + foreach ($casesExecuted as $caseNumber) { + $this->saveLog("unassignedCase", "action", "OK Executed trigger to the case $caseNumber"); + } + $this->setExecutionResultMessage(count($casesExecuted) . " Cases"); + } catch (Exception $e) { + $this->setExecutionResultMessage("WITH ERRORS", "error"); + $this->saveLog("unassignedCase", "action", "Unassigned case", "c"); + if ($this->asynchronous === false) { + eprintln(" '-" . $e->getMessage(), "red"); + } + $this->saveLog("unassignedCase", "error", "Error in unassigned case: " . $e->getMessage()); + } + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * This calculate duration. + */ + public function calculateDuration() + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($scheduledTaskIdentifier) { + $this->setExecutionMessage("Calculating Duration"); + try { + $appDelegation = new AppDelegation(); + $appDelegation->calculateDuration(1); + $this->setExecutionResultMessage('DONE'); + $this->saveLog('calculateDuration', 'action', 'Calculating Duration'); + } catch (Exception $e) { + $this->setExecutionResultMessage('WITH ERRORS', 'error'); + if ($this->asynchronous === false) { + eprintln(" '-" . $e->getMessage(), 'red'); + } + $this->saveLog('calculateDuration', 'error', 'Error Calculating Duration: ' . $e->getMessage()); + } + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * This calculate application duration. + */ + public function calculateAppDuration() + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($scheduledTaskIdentifier) { + $this->setExecutionMessage("Calculating Duration by Application"); + try { + $application = new Application(); + $application->calculateAppDuration(1); + $this->setExecutionResultMessage('DONE'); + $this->saveLog('calculateDurationByApp', 'action', 'Calculating Duration by Application'); + } catch (Exception $e) { + $this->setExecutionResultMessage('WITH ERRORS', 'error'); + if ($this->asynchronous === false) { + eprintln(" '-" . $e->getMessage(), 'red'); + } + $this->saveLog('calculateDurationByApp', 'error', 'Error Calculating Duration: ' . $e->getMessage()); + } + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * Clean unused records in tables related to the Self-Service Value Based feature. + */ + public function cleanSelfServiceTables() + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($scheduledTaskIdentifier) { + try { + // Start message + $this->setExecutionMessage("Clean unused records for Self-Service Value Based feature"); + + // Get Propel connection + $cnn = Propel::getConnection(AppAssignSelfServiceValueGroupPeer::DATABASE_NAME); + + // Delete related rows and missing relations, criteria don't execute delete with joins + $cnn->begin(); + $stmt = $cnn->createStatement(); + $stmt->executeQuery("DELETE " . AppAssignSelfServiceValueGroupPeer::TABLE_NAME . " + FROM " . AppAssignSelfServiceValueGroupPeer::TABLE_NAME . " + LEFT JOIN " . AppAssignSelfServiceValuePeer::TABLE_NAME . " + ON (" . AppAssignSelfServiceValueGroupPeer::ID . " = " . AppAssignSelfServiceValuePeer::ID . ") + WHERE " . AppAssignSelfServiceValuePeer::ID . " IS NULL"); + $cnn->commit(); + + // Success message + $this->setExecutionResultMessage("DONE"); + } catch (Exception $e) { + $cnn->rollback(); + $this->setExecutionResultMessage("WITH ERRORS", "error"); + if ($this->asynchronous === false) { + eprintln(" '-" . $e->getMessage(), "red"); + } + $this->saveLog("ExecuteCleanSelfServiceTables", "error", "Error when try to clean self-service tables " . $e->getMessage()); + } + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * This execute plugins cron. + * @return boolean + */ + public function executePlugins() + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($scheduledTaskIdentifier) { + $pathCronPlugins = PATH_CORE . 'bin' . PATH_SEP . 'plugins' . PATH_SEP; + + // Executing cron files in bin/plugins directory + if (!is_dir($pathCronPlugins)) { + return false; + } + + if ($handle = opendir($pathCronPlugins)) { + $this->setExecutionMessage('Executing cron files in bin/plugins directory in Workspace: ' . config("system.workspace")); + while (false !== ($file = readdir($handle))) { + if (strpos($file, '.php', 1) && is_file($pathCronPlugins . $file)) { + $filename = str_replace('.php', '', $file); + $className = $filename . 'ClassCron'; + + // Execute custom cron function + $this->executeCustomCronFunction($pathCronPlugins . $file, $className); + } + } + } + + // Executing registered cron files + // -> Get registered cron files + $pluginRegistry = PluginRegistry::loadSingleton(); + $cronFiles = $pluginRegistry->getCronFiles(); + + // -> Execute functions + if (!empty($cronFiles)) { + $this->setExecutionMessage('Executing registered cron files for Workspace: ' . config('system.workspace')); + /** + * @var \ProcessMaker\Plugins\Interfaces\CronFile $cronFile + */ + foreach ($cronFiles as $cronFile) { + $path = PATH_PLUGINS . $cronFile->getNamespace() . PATH_SEP . 'bin' . PATH_SEP . $cronFile->getCronFile() . '.php'; + if (file_exists($path)) { + $this->executeCustomCronFunction($path, $cronFile->getCronFile()); + } else { + $this->setExecutionMessage('File ' . $cronFile->getCronFile() . '.php ' . 'does not exist.'); + } + } + } + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * This execute custom cron function. + * @param string $pathFile + * @param string $className + */ + public function executeCustomCronFunction($pathFile, $className) + { + include_once $pathFile; + + $plugin = new $className(); + + if (method_exists($plugin, 'executeCron')) { + $arrayCron = unserialize(trim(@file_get_contents(PATH_DATA . "cron"))); + $arrayCron["processcTimeProcess"] = 60; //Minutes + $arrayCron["processcTimeStart"] = time(); + @file_put_contents(PATH_DATA . "cron", serialize($arrayCron)); + + //Try to execute Plugin Cron. If there is an error then continue with the next file + $this->setExecutionMessage("\n--- Executing cron file: $pathFile"); + try { + $plugin->executeCron(); + $this->setExecutionResultMessage('DONE'); + } catch (Exception $e) { + $this->setExecutionResultMessage('FAILED', 'error'); + if ($this->asynchronous === false) { + eprintln(" '-" . $e->getMessage(), 'red'); + } + $this->saveLog('executePlugins', 'error', 'Error executing cron file: ' . $pathFile . ' - ' . $e->getMessage()); + } + } + } + + /** + * This fills the report by user. + * @param string $dateInit + * @param string $dateFinish + * @return boolean + */ + public function fillReportByUser($dateInit, $dateFinish) + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + if ($dateInit == null) { + if ($this->asynchronous === false) { + eprintln("You must enter the starting date.", "red"); + eprintln('Example: +init-date"YYYY-MM-DD HH:MM:SS" +finish-date"YYYY-MM-DD HH:MM:SS"', "red"); + } + if ($this->asynchronous === true) { + $message = 'You must enter the starting date. Example: +init-date"YYYY-MM-DD HH:MM:SS" +finish-date"YYYY-MM-DD HH:MM:SS"'; + Log::channel('taskScheduler:taskScheduler')->info($message, Bootstrap::context()); + } + return false; + } + $job = function() use($dateInit, $dateFinish, $scheduledTaskIdentifier) { + try { + + $dateFinish = ($dateFinish != null) ? $dateFinish : date("Y-m-d H:i:s"); + + $appCacheView = new AppCacheView(); + $appCacheView->setPathToAppCacheFiles(PATH_METHODS . 'setup' . PATH_SEP . 'setupSchemas' . PATH_SEP); + $this->setExecutionMessage("Calculating data to fill the 'User Reporting'..."); + $appCacheView->fillReportByUser($dateInit, $dateFinish); + setExecutionResultMessage("DONE"); + } catch (Exception $e) { + $this->setExecutionResultMessage("WITH ERRORS", "error"); + if ($this->asynchronous === false) { + eprintln(" '-" . $e->getMessage(), "red"); + } + $this->saveLog("fillReportByUser", "error", "Error in fill report by user: " . $e->getMessage()); + } + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * This fills the report by process. + * @param string $dateInit + * @param string $dateFinish + * @return boolean + */ + public function fillReportByProcess($dateInit, $dateFinish) + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + if ($dateInit == null) { + if ($this->asynchronous === false) { + eprintln("You must enter the starting date.", "red"); + eprintln('Example: +init-date"YYYY-MM-DD HH:MM:SS" +finish-date"YYYY-MM-DD HH:MM:SS"', "red"); + } + if ($this->asynchronous === true) { + $message = 'You must enter the starting date. Example: +init-date"YYYY-MM-DD HH:MM:SS" +finish-date"YYYY-MM-DD HH:MM:SS"'; + Log::channel('taskScheduler:taskScheduler')->info($message, Bootstrap::context()); + } + return false; + } + $job = function() use($dateInit, $dateFinish, $scheduledTaskIdentifier) { + try { + + $dateFinish = ($dateFinish != null) ? $dateFinish : date("Y-m-d H:i:s"); + $appcv = new AppCacheView(); + $appcv->setPathToAppCacheFiles(PATH_METHODS . 'setup' . PATH_SEP . 'setupSchemas' . PATH_SEP); + + $this->setExecutionMessage("Calculating data to fill the 'Process Reporting'..."); + $appcv->fillReportByProcess($dateInit, $dateFinish); + $this->setExecutionResultMessage("DONE"); + } catch (Exception $e) { + $this->setExecutionResultMessage("WITH ERRORS", "error"); + if ($this->asynchronous === false) { + eprintln(" '-" . $e->getMessage(), "red"); + } + $this->saveLog("fillReportByProcess", "error", "Error in fill report by process: " . $e->getMessage()); + } + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * This execute ldap cron. + * @param boolean $debug + */ + public function ldapcron($debug) + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($debug, $scheduledTaskIdentifier) { + require_once(PATH_HOME . 'engine' . PATH_SEP . 'methods' . PATH_SEP . 'services' . PATH_SEP . 'ldapadvanced.php'); + $ldapadvancedClassCron = new ldapadvancedClassCron(); + $ldapadvancedClassCron->executeCron($debug); + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * This execute the sending of notifications. + */ + function sendNotifications() + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($scheduledTaskIdentifier) { + try { + $this->setExecutionMessage("Resending Notifications"); + $this->setExecutionResultMessage("PROCESSING"); + $notQueue = new NotificationQueue(); + $notQueue->checkIfCasesOpenForResendingNotification(); + $notificationsAndroid = $notQueue->loadStatusDeviceType('pending', 'android'); + if ($notificationsAndroid) { + $this->setExecutionMessage("|-- Send Android's Notifications"); + $n = 0; + foreach ($notificationsAndroid as $key => $item) { + $notification = new PushMessageAndroid(); + $notification->setSettingNotification(); + $notification->setDevices(unserialize($item['DEV_UID'])); + $response['android'] = $notification->send($item['NOT_MSG'], unserialize($item['NOT_DATA'])); + $notQueue = new NotificationQueue(); + $notQueue->changeStatusSent($item['NOT_UID']); + $n += $notification->getNumberDevices(); + } + $this->setExecutionResultMessage("Processed $n"); + } + $notificationsApple = $notQueue->loadStatusDeviceType('pending', 'apple'); + if ($notificationsApple) { + $this->setExecutionMessage("|-- Send Apple Notifications"); + $n = 0; + foreach ($notificationsApple as $key => $item) { + $notification = new PushMessageIOS(); + $notification->setSettingNotification(); + $notification->setDevices(unserialize($item['DEV_UID'])); + $response['apple'] = $notification->send($item['NOT_MSG'], unserialize($item['NOT_DATA'])); + $notQueue = new NotificationQueue(); + $notQueue->changeStatusSent($item['NOT_UID']); + $n += $notification->getNumberDevices(); + } + $this->setExecutionResultMessage("Processed $n"); + } + } catch (Exception $e) { + $this->setExecutionResultMessage("WITH ERRORS", "error"); + if ($this->asynchronous === false) { + eprintln(" '-" . $e->getMessage(), "red"); + } + $this->saveLog("ExecuteSendNotifications", "error", "Error when sending notifications " . $e->getMessage()); + } + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * This executes an actions by email responses. + */ + public function actionsByEmailResponse() + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($scheduledTaskIdentifier) { + $responseReader = new ResponseReader(); + $responseReader->actionsByEmailEmailResponse(); + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * This execute message event cron. + */ + public function messageeventcron() + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use($scheduledTaskIdentifier) { + $messageApplication = new MessageApplication(); + $messageApplication->catchMessageEvent(true); + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } + + /** + * Start/Continue cases by Timer-Event + * + * @param string $datetime + * @param bool $frontEnd + */ + public function timerEventCron($datetime, $frontEnd) + { + $scheduledTaskIdentifier = uniqid(__FUNCTION__ . "#"); + Log::channel('taskScheduler:taskScheduler')->info("Start {$scheduledTaskIdentifier}", Bootstrap::context()); + + $job = function() use ($datetime, $frontEnd, $scheduledTaskIdentifier) { + $timerEvent = new TimerEvent(); + $timerEvent->startContinueCaseByTimerEvent($datetime, $frontEnd); + + Log::channel('taskScheduler:taskScheduler')->info("Finish {$scheduledTaskIdentifier}", Bootstrap::context()); + }; + $this->runTask($job); + } +} diff --git a/workflow/engine/src/ProcessMaker/Util/helpers.php b/workflow/engine/src/ProcessMaker/Util/helpers.php index dac3f1644..c28ad2b09 100644 --- a/workflow/engine/src/ProcessMaker/Util/helpers.php +++ b/workflow/engine/src/ProcessMaker/Util/helpers.php @@ -640,3 +640,33 @@ function saveAppDocument($file, $appUid, $appDocUid, $version = 1, $upload = tru throw $e; } } + +/** + * Add a specific date minutes, hours or days + * + * @param string $iniDate + * @param string $timeUnit + * @param int $time + * + * @return string + * + * @link https://www.php.net/manual/en/datetime.modify.php + */ +function calculateDate($iniDate, $timeUnit, $time) +{ + + $datetime = new DateTime($iniDate); + switch ($timeUnit) { + case 'DAYS': + $datetime->modify('+' . $time . ' day'); + break; + case 'HOURS': + $datetime->modify('+' . $time . ' hour'); + break; + case 'MINUTES': + $datetime->modify('+' . $time . ' minutes'); + break; + } + + return $datetime->format('Y-m-d H:i:s'); +}