From 5041c9be3d84c2c06838e643fb2826569d0b11c6 Mon Sep 17 00:00:00 2001 From: Roly Rudy Gutierrez Pinto Date: Tue, 19 May 2020 12:15:46 -0400 Subject: [PATCH 01/24] PMCORE-1404 Use Jobs in the 'script task' and 'service task' execution --- app/Jobs/CasesDispatch.php | 8 + database/factories/ApplicationFactory.php | 42 ++- database/factories/DelegationFactory.php | 40 ++- database/factories/GroupwfFactory.php | 2 +- database/factories/ProcessFactory.php | 11 +- database/factories/StepTriggerFactory.php | 18 + database/factories/TaskFactory.php | 9 +- database/factories/TriggerFactory.php | 8 +- .../src/ProcessMaker/Cases/CasesTraitTest.php | 291 +++++++++++++++++ .../src/ProcessMaker/Model/DelegationTest.php | 32 +- .../src/ProcessMaker/Model/ProcessTest.php | 8 +- .../Services/OAuth2/ServerTest.php | 7 +- workflow/engine/classes/Cases.php | 2 + workflow/engine/classes/Derivation.php | 2 +- workflow/engine/classes/class.pmScript.php | 11 +- .../engine/methods/cases/cases_Derivate.php | 308 ++++-------------- .../src/ProcessMaker/Cases/CasesTrait.php | 205 ++++++++++++ .../src/ProcessMaker/Model/StepTrigger.php | 14 + .../src/ProcessMaker/Model/Triggers.php | 2 + 19 files changed, 711 insertions(+), 309 deletions(-) create mode 100644 app/Jobs/CasesDispatch.php create mode 100644 database/factories/StepTriggerFactory.php create mode 100644 tests/unit/workflow/engine/src/ProcessMaker/Cases/CasesTraitTest.php create mode 100644 workflow/engine/src/ProcessMaker/Cases/CasesTrait.php create mode 100644 workflow/engine/src/ProcessMaker/Model/StepTrigger.php diff --git a/app/Jobs/CasesDispatch.php b/app/Jobs/CasesDispatch.php new file mode 100644 index 000000000..4ea2cb317 --- /dev/null +++ b/app/Jobs/CasesDispatch.php @@ -0,0 +1,8 @@ +define(\ProcessMaker\Model\Application::class, function(Faker $faker) { - $statuses = ['DRAFT', 'TO_DO']; - $status = $faker->randomElement($statuses); - $statusId = array_search($status, $statuses) + 1; + $user = factory(\ProcessMaker\Model\User::class)->create(); $appNumber = $faker->unique()->numberBetween(1000); + + //APP_TITLE field is used in 'MYSQL: MATCH() AGAINST()' function, string size should not be less than 3. + $appTitle = $faker->lexify(str_repeat('?', rand(3, 5)) . ' ' . str_repeat('?', rand(3, 5))); + + //APP_STATUS must start in TO_DO because all tests require this state. + return [ 'APP_UID' => G::generateUniqueID(), - 'APP_TITLE' => $faker->sentence(3), + 'APP_TITLE' => $appTitle, 'APP_NUMBER' => $appNumber, - 'APP_STATUS' => $status, - 'APP_STATUS_ID' => $statusId, - 'PRO_UID' => G::generateUniqueID(), + 'APP_STATUS' => 'TO_DO', + 'APP_STATUS_ID' => 2, + 'PRO_UID' => function() { + return factory(\ProcessMaker\Model\Process::class)->create()->PRO_UID; + }, 'APP_PARALLEL' => 'N', - 'APP_INIT_USER' => G::generateUniqueID(), - 'APP_CUR_USER' => G::generateUniqueID(), + 'APP_INIT_USER' => $user->USR_UID, + 'APP_CUR_USER' => $user->USR_UID, 'APP_PIN' => G::generateUniqueID(), 'APP_CREATE_DATE' => $faker->dateTime(), 'APP_UPDATE_DATE' => $faker->dateTime(), @@ -30,17 +36,19 @@ $factory->state(\ProcessMaker\Model\Application::class, 'foreign_keys', function // Create values in the foreign key relations $process = factory(\ProcessMaker\Model\Process::class)->create(); $user = factory(\ProcessMaker\Model\User::class)->create(); - // Get other random values - $statuses = ['DRAFT', 'TO_DO']; - $status = $faker->randomElement($statuses); - $statusId = array_search($status, $statuses) + 1; $appNumber = $faker->unique()->numberBetween(1000); + + //APP_TITLE field is used in 'MYSQL: MATCH() AGAINST()' function, string size should not be less than 3. + $appTitle = $faker->lexify(str_repeat('?', rand(3, 5)) . ' ' . str_repeat('?', rand(3, 5))); + + //APP_STATUS must start in TO_DO because all tests require this state. + return [ 'APP_UID' => G::generateUniqueID(), - 'APP_TITLE' => $faker->sentence(3), + 'APP_TITLE' => $appTitle, 'APP_NUMBER' => $appNumber, - 'APP_STATUS' => $status, - 'APP_STATUS_ID' => $statusId, + 'APP_STATUS' => 'TO_DO', + 'APP_STATUS_ID' => 2, 'PRO_UID' => $process->PRO_UID, 'APP_PARALLEL' => 'N', 'APP_INIT_USER' => $user->USR_UID, @@ -51,4 +59,4 @@ $factory->state(\ProcessMaker\Model\Application::class, 'foreign_keys', function 'APP_INIT_DATE' => $faker->dateTime(), 'APP_DATA' => serialize(['APP_NUMBER' => $appNumber]) ]; -}); \ No newline at end of file +}); diff --git a/database/factories/DelegationFactory.php b/database/factories/DelegationFactory.php index edfc13b01..fffbb43c4 100644 --- a/database/factories/DelegationFactory.php +++ b/database/factories/DelegationFactory.php @@ -3,15 +3,26 @@ use Faker\Generator as Faker; $factory->define(\ProcessMaker\Model\Delegation::class, function(Faker $faker) { + $user = factory(\ProcessMaker\Model\User::class)->create(); + $process = factory(\ProcessMaker\Model\Process::class)->create(); + $task = factory(\ProcessMaker\Model\Task::class)->create([ + 'PRO_UID' => $process->PRO_UID, + 'PRO_ID' => $process->PRO_ID + ]); + $application = factory(\ProcessMaker\Model\Application::class)->create([ + 'PRO_UID' => $process->PRO_UID, + 'APP_INIT_USER' => $user->USR_UID, + 'APP_CUR_USER' => $user->USR_UID + ]); // Return with default values return [ - 'APP_UID' => G::generateUniqueID(), + 'APP_UID' => $application->APP_UID, 'DEL_INDEX' => 1, - 'APP_NUMBER' => $faker->unique()->numberBetween(1, 100000), + 'APP_NUMBER' => $application->APP_NUMBER, 'DEL_PREVIOUS' => 0, - 'PRO_UID' => G::generateUniqueID(), - 'TAS_UID' => G::generateUniqueID(), - 'USR_UID' => G::generateUniqueID(), + 'PRO_UID' => $process->PRO_UID, + 'TAS_UID' => $task->TAS_UID, + 'USR_UID' => $user->USR_UID, 'DEL_TYPE' => 'NORMAL', 'DEL_THREAD' => 1, 'DEL_THREAD_STATUS' => 'OPEN', @@ -20,9 +31,9 @@ $factory->define(\ProcessMaker\Model\Delegation::class, function(Faker $faker) { 'DEL_INIT_DATE' => $faker->dateTime(), 'DEL_TASK_DUE_DATE' => $faker->dateTime(), 'DEL_RISK_DATE' => $faker->dateTime(), - 'USR_ID' => 0, - 'PRO_ID' => 0, - 'TAS_ID' => 0, + 'USR_ID' => $user->USR_ID, + 'PRO_ID' => $process->PRO_ID, + 'TAS_ID' => $task->TAS_ID, 'DEL_DATA' => '' ]; }); @@ -30,10 +41,17 @@ $factory->define(\ProcessMaker\Model\Delegation::class, function(Faker $faker) { // Create a delegation with the foreign keys $factory->state(\ProcessMaker\Model\Delegation::class, 'foreign_keys', function (Faker $faker) { // Create values in the foreign key relations - $application = factory(\ProcessMaker\Model\Application::class)->create(); - $process = factory(\ProcessMaker\Model\Process::class)->create(); - $task = factory(\ProcessMaker\Model\Task::class)->create(); $user = factory(\ProcessMaker\Model\User::class)->create(); + $process = factory(\ProcessMaker\Model\Process::class)->create(); + $task = factory(\ProcessMaker\Model\Task::class)->create([ + 'PRO_UID' => $process->PRO_UID, + 'PRO_ID' => $process->PRO_ID + ]); + $application = factory(\ProcessMaker\Model\Application::class)->create([ + 'PRO_UID' => $process->PRO_UID, + 'APP_INIT_USER' => $user->USR_UID, + 'APP_CUR_USER' => $user->USR_UID + ]); // Return with default values return [ diff --git a/database/factories/GroupwfFactory.php b/database/factories/GroupwfFactory.php index 7ab6ca271..0a7e77198 100644 --- a/database/factories/GroupwfFactory.php +++ b/database/factories/GroupwfFactory.php @@ -7,7 +7,7 @@ use Faker\Generator as Faker; $factory->define(\ProcessMaker\Model\Groupwf::class, function(Faker $faker) { return [ 'GRP_UID' => G::generateUniqueID(), - 'GRP_ID' => $faker->unique()->numberBetween(1, 2000), + //'GRP_ID' The incremental fields of the tables must not be specified in the creation list. 'GRP_TITLE' => $faker->sentence(2), 'GRP_STATUS' => 'ACTIVE', 'GRP_LDAP_DN' => '', diff --git a/database/factories/ProcessFactory.php b/database/factories/ProcessFactory.php index 4cd3e026c..5465309c2 100644 --- a/database/factories/ProcessFactory.php +++ b/database/factories/ProcessFactory.php @@ -6,9 +6,10 @@ use Faker\Generator as Faker; $factory->define(\ProcessMaker\Model\Process::class, function(Faker $faker) { // Return with default values + //The incremental fields of the tables must not be specified in the creation list. return [ 'PRO_UID' => G::generateUniqueID(), - 'PRO_ID' => $faker->unique()->numberBetween(1, 1000000), + //'PRO_ID' The incremental fields of the tables must not be specified in the creation list. 'PRO_TITLE' => $faker->sentence(3), 'PRO_DESCRIPTION' => $faker->paragraph(3), 'PRO_CREATE_USER' => '00000000000000000000000000000001', @@ -19,7 +20,9 @@ $factory->define(\ProcessMaker\Model\Process::class, function(Faker $faker) { 'PRO_TYPE_PROCESS' => 'PUBLIC', 'PRO_UPDATE_DATE' => $faker->dateTime(), 'PRO_CREATE_DATE' => $faker->dateTime(), - 'PRO_CATEGORY' => '', + 'PRO_CATEGORY' => function() { + return factory(\ProcessMaker\Model\ProcessCategory::class)->create()->CATEGORY_UID; + }, ]; }); @@ -28,7 +31,7 @@ $factory->state(\ProcessMaker\Model\Process::class, 'foreign_keys', function (Fa $user = factory(\ProcessMaker\Model\User::class)->create(); return [ 'PRO_UID' => G::generateUniqueID(), - 'PRO_ID' => $faker->unique()->numberBetween(1, 1000000), + //'PRO_ID' The incremental fields of the tables must not be specified in the creation list. 'PRO_TITLE' => $faker->sentence(3), 'PRO_DESCRIPTION' => $faker->paragraph(3), 'PRO_CREATE_USER' => $user->USR_UID, @@ -49,7 +52,7 @@ $factory->state(\ProcessMaker\Model\Process::class, 'flow', function (Faker $fak $user = factory(\ProcessMaker\Model\User::class)->create(); $process = [ 'PRO_UID' => G::generateUniqueID(), - 'PRO_ID' => $faker->unique()->numberBetween(1, 1000000), + //'PRO_ID' The incremental fields of the tables must not be specified in the creation list. 'PRO_TITLE' => $faker->sentence(3), 'PRO_DESCRIPTION' => $faker->paragraph(3), 'PRO_CREATE_USER' => $user->USR_UID, diff --git a/database/factories/StepTriggerFactory.php b/database/factories/StepTriggerFactory.php new file mode 100644 index 000000000..6d63cdd80 --- /dev/null +++ b/database/factories/StepTriggerFactory.php @@ -0,0 +1,18 @@ +define(\ProcessMaker\Model\StepTrigger::class, function (Faker $faker) { + return [ + 'STEP_UID' => $faker->regexify("/[a-zA-Z]{32}/"), + 'TAS_UID' => function() { + return factory(\ProcessMaker\Model\Task::class)->create()->TAS_UID; + }, + 'TRI_UID' => function() { + return factory(\ProcessMaker\Model\Triggers::class)->create()->TRI_UID; + }, + 'ST_TYPE' => 'BEFORE', + 'ST_CONDITION' => '', + 'ST_POSITION' => 1, + ]; +}); diff --git a/database/factories/TaskFactory.php b/database/factories/TaskFactory.php index a8347b2f9..367734efb 100644 --- a/database/factories/TaskFactory.php +++ b/database/factories/TaskFactory.php @@ -6,11 +6,12 @@ use Faker\Generator as Faker; $factory->define(\ProcessMaker\Model\Task::class, function(Faker $faker) { + $process = factory(\ProcessMaker\Model\Process::class)->create(); return [ - 'PRO_UID' => G::generateUniqueID(), - 'PRO_ID' => $faker->unique()->numberBetween(), + 'PRO_UID' => $process->PRO_UID, + 'PRO_ID' => $process->PRO_ID, 'TAS_UID' => G::generateUniqueID(), - 'TAS_ID' => $faker->unique()->numberBetween(), + //'TAS_ID' The incremental fields of the tables must not be specified in the creation list. 'TAS_TITLE' => $faker->sentence(2), 'TAS_TYPE' => 'NORMAL', 'TAS_TYPE_DAY' => 1, @@ -39,7 +40,7 @@ $factory->state(\ProcessMaker\Model\Task::class, 'foreign_keys', function (Faker 'PRO_UID' => $process->PRO_UID, 'PRO_ID' => $process->PRO_ID, 'TAS_UID' => G::generateUniqueID(), - 'TAS_ID' => $faker->unique()->numberBetween(1, 200000), + //'TAS_ID' The incremental fields of the tables must not be specified in the creation list. 'TAS_TITLE' => $faker->sentence(2), 'TAS_TYPE' => 'NORMAL', 'TAS_TYPE_DAY' => 1, diff --git a/database/factories/TriggerFactory.php b/database/factories/TriggerFactory.php index e0b9adfe6..f467c626a 100644 --- a/database/factories/TriggerFactory.php +++ b/database/factories/TriggerFactory.php @@ -5,12 +5,14 @@ use ProcessMaker\Model\Triggers; $factory->define(Triggers::class, function (Faker $faker) { return [ - 'TRI_UID' => G::generateUniqueID(), + 'TRI_UID' => $faker->regexify("/[a-zA-Z]{32}/"), 'TRI_TITLE' => $faker->sentence(5), 'TRI_DESCRIPTION' => $faker->text, - 'PRO_UID' => G::generateUniqueID(), + 'PRO_UID' => function() { + return factory(\ProcessMaker\Model\Process::class)->create()->PRO_UID; + }, 'TRI_TYPE' => 'SCRIPT', 'TRI_WEBBOT' => $faker->text, 'TRI_PARAM' => '', ]; -}); \ No newline at end of file +}); diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Cases/CasesTraitTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Cases/CasesTraitTest.php new file mode 100644 index 000000000..60a064336 --- /dev/null +++ b/tests/unit/workflow/engine/src/ProcessMaker/Cases/CasesTraitTest.php @@ -0,0 +1,291 @@ +get()->first(); + + $process = factory(Process::class)->create([ + 'PRO_CREATE_USER' => $user->USR_UID + ]); + $task = factory(Task::class)->create([ + 'TAS_ASSIGN_TYPE' => 'BALANCED', + 'TAS_GROUP_VARIABLE' => '', + 'PRO_UID' => $process->PRO_UID + ]); + factory(TaskUser::class)->create([ + 'TAS_UID' => $task->TAS_UID, + 'USR_UID' => $user->USR_UID, + 'TU_RELATION' => 1, + 'TU_TYPE' => 1 + ]); + $task2 = factory(Task::class)->create([ + 'TAS_ASSIGN_TYPE' => 'BALANCED', + 'TAS_GROUP_VARIABLE' => '', + 'PRO_UID' => $process->PRO_UID + ]); + factory(TaskUser::class)->create([ + 'TAS_UID' => $task2->TAS_UID, + 'USR_UID' => $user->USR_UID, + 'TU_RELATION' => 1, + 'TU_TYPE' => 1 + ]); + + + $application = factory(Application::class)->create([ + 'PRO_UID' => $process->PRO_UID + ]); + $appDelegation = factory(Delegation::class)->create([ + 'USR_UID' => $user->USR_UID, + 'PRO_UID' => $process->PRO_UID, + 'APP_UID' => $application->APP_UID, + 'TAS_UID' => $task->TAS_UID, + 'DEL_INDEX' => 1, + ]); + factory(Delegation::class)->create([ + 'USR_UID' => $user->USR_UID, + 'PRO_UID' => $process->PRO_UID, + 'APP_UID' => $application->APP_UID, + 'TAS_UID' => $task2->TAS_UID, + 'DEL_INDEX' => 2, + 'DEL_PREVIOUS' => $appDelegation->DEL_INDEX + ]); + factory(Route::class)->create([ + 'TAS_UID' => $task->TAS_UID, + 'ROU_NEXT_TASK' => $task2->TAS_UID, + 'PRO_UID' => $process->PRO_UID + ]); + + $step = factory(Step::class)->create([ + 'PRO_UID' => $process->PRO_UID, + 'TAS_UID' => $appDelegation->TAS_UID, + 'STEP_POSITION' => 2, + 'STEP_CONDITION' => '1 == 1' + ]); + + + $triggers = factory(Triggers::class)->create([ + 'PRO_UID' => $process->PRO_UID, + 'TRI_WEBBOT' => '$a = 0;' + ]); + factory(StepTrigger::class)->create([ + 'STEP_UID' => -2, + 'TAS_UID' => $task->TAS_UID, + 'TRI_UID' => $triggers->TRI_UID, + 'ST_CONDITION' => '1 == 1', + 'ST_TYPE' => 'BEFORE' + ]); + + + $triggers = factory(Triggers::class)->create([ + 'PRO_UID' => $process->PRO_UID, + 'TRI_WEBBOT' => '$b = 0;' + ]); + factory(StepTrigger::class)->create([ + 'STEP_UID' => -2, + 'TAS_UID' => $task->TAS_UID, + 'TRI_UID' => $triggers->TRI_UID, + 'ST_CONDITION' => '2 == 2', + 'ST_TYPE' => 'AFTER' + ]); + + $result = [ + 'application' => $application, + 'user' => $user, + 'task' => $task, + 'task2' => $task2, + ]; + return (object) $result; + } + + /** + * This test verifies that the derivation of the case has closed the delegation + * with single task. + * @test + * @covers \Cases::routeCase + */ + public function it_should_test_a_derivate_case_with_single_task() + { + $result = $this->prepareDerivationData(); + $application = $result->application; + $user = $result->user; + $task = $result->task; + $task2 = $result->task2; + + $processUid = $application->PRO_UID; + $application = $application->APP_UID; + $postForm = [ + 'ROU_TYPE' => 'SEQUENTIAL', + 'TASKS' => [ + 1 => [ + 'TAS_UID' => $task->TAS_UID, + 'USR_UID' => $user->USR_UID, + 'TAS_ASSIGN_TYPE' => "BALANCED", + 'TAS_DEF_PROC_CODE' => "", + 'DEL_PRIORITY' => "", + 'TAS_PARENT' => "", + 'ROU_CONDITION' => "", + 'SOURCE_UID' => $task->TAS_UID, + ] + ] + ]; + $status = 'TO_DO'; + $flagGmail = true; + $tasUid = $task->TAS_UID; + $index = '1'; + $userLogged = $user->USR_UID; + + $cases = new Cases(); + $cases->routeCase($processUid, $application, $postForm, $status, $flagGmail, $tasUid, $index, $userLogged); + + $result = Delegation::where('APP_UID', '=', $application)->where('DEL_INDEX', '=', $index)->get()->first(); + + $this->assertEquals('CLOSED', $result->DEL_THREAD_STATUS); + } + + /** + * This test verifies that the derivation of the case has closed the delegation + * with multiple task. + * @test + * @covers \Cases::routeCase + */ + public function it_should_test_a_derivate_case_with_multiple_task() + { + $result = $this->prepareDerivationData(); + $application = $result->application; + $user = $result->user; + $task = $result->task; + $task2 = $result->task2; + + $processUid = $application->PRO_UID; + $application = $application->APP_UID; + $postForm = [ + 'ROU_TYPE' => 'SEQUENTIAL', + 'TASKS' => [ + 1 => [ + 'TAS_UID' => $task->TAS_UID, + 'USR_UID' => $user->USR_UID, + 'TAS_ASSIGN_TYPE' => "BALANCED", + 'TAS_DEF_PROC_CODE' => "", + 'DEL_PRIORITY' => "", + 'TAS_PARENT' => "", + 'ROU_CONDITION' => "", + 'SOURCE_UID' => $task->TAS_UID, + ], + 2 => [ + 'TAS_UID' => $task2->TAS_UID, + 'USR_UID' => $user->USR_UID, + 'TAS_ASSIGN_TYPE' => "BALANCED", + 'TAS_DEF_PROC_CODE' => "", + 'DEL_PRIORITY' => "", + 'TAS_PARENT' => "", + 'ROU_CONDITION' => "", + 'SOURCE_UID' => $task2->TAS_UID, + ] + ] + ]; + $status = 'TO_DO'; + $flagGmail = true; + $tasUid = $task->TAS_UID; + $index = '1'; + $userLogged = $user->USR_UID; + + $cases = new Cases(); + $cases->routeCase($processUid, $application, $postForm, $status, $flagGmail, $tasUid, $index, $userLogged); + + $result = Delegation::where('APP_UID', '=', $application)->where('DEL_INDEX', '=', $index)->get()->first(); + + $this->assertEquals('CLOSED', $result->DEL_THREAD_STATUS); + } + + /** + * This test verifies that the 'CasesDispath' job is in the queue. + * @test + * @covers \Cases::routeCase + */ + public function this_should_add_a_cases_dispath_job_to_the_queue() + { + $result = $this->prepareDerivationData(); + $application = $result->application; + $user = $result->user; + $task = $result->task; + $task2 = $result->task2; + + $_SESSION['PROCESS'] = $application->PRO_UID; + $_SESSION['APPLICATION'] = $application->APP_UID; + $_POST['form'] = [ + 'ROU_TYPE' => 'SEQUENTIAL', + 'TASKS' => [ + 1 => [ + 'TAS_UID' => $task->TAS_UID, + 'USR_UID' => $user->USR_UID, + 'TAS_ASSIGN_TYPE' => "BALANCED", + 'TAS_DEF_PROC_CODE' => "", + 'DEL_PRIORITY' => "", + 'TAS_PARENT' => "", + 'ROU_CONDITION' => "", + 'SOURCE_UID' => $task->TAS_UID, + ], + 2 => [ + 'TAS_UID' => $task2->TAS_UID, + 'USR_UID' => $user->USR_UID, + 'TAS_ASSIGN_TYPE' => "BALANCED", + 'TAS_DEF_PROC_CODE' => "", + 'DEL_PRIORITY' => "", + 'TAS_PARENT' => "", + 'ROU_CONDITION' => "", + 'SOURCE_UID' => $task2->TAS_UID, + ] + ] + ]; + $status = 'TO_DO'; + $flagGmail = true; + $_SESSION['TASK'] = $task->TAS_UID; + $_SESSION["INDEX"] = '1'; + $_SESSION['USER_LOGGED'] = $user->USR_UID; + + global $RBAC; + $RBAC = RBAC::getSingleton(PATH_DATA, session_id()); + $RBAC->initRBAC(); + $RBAC->loadUserRolePermission('PROCESSMAKER', $_SESSION['USER_LOGGED']); + + Queue::fake(); + Queue::assertNothingPushed(); + + require_once PATH_METHODS . 'cases/cases_Derivate.php'; + + Queue::assertPushed(CasesDispatch::class); + } +} diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Model/DelegationTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Model/DelegationTest.php index a9314cd26..de9231d22 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Model/DelegationTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Model/DelegationTest.php @@ -27,6 +27,15 @@ class DelegationTest extends TestCase { use DatabaseTransactions; + /** + * Set up function. + */ + public function setUp() + { + parent::setUp(); + Delegation::truncate(); + } + /** * This checks to make sure pagination is working properly * @@ -255,6 +264,7 @@ class DelegationTest extends TestCase $this->assertCount(1, $results['data']); $this->assertEquals($application->APP_NUMBER, $results['data'][0]['APP_NUMBER']); } + /** * This ensures searching by case number and review the order * @@ -263,28 +273,23 @@ class DelegationTest extends TestCase */ public function it_should_search_and_filter_by_app_title() { - $title = ''; - for ($x = 1; $x <= 10; $x++) { - $application = factory(Application::class)->states('foreign_keys')->create(); - factory(Delegation::class)->states('foreign_keys')->create([ - 'APP_UID' => $application->APP_UID, - 'APP_NUMBER' => $application->APP_NUMBER, - 'PRO_UID' => $application->PRO_UID, - 'PRO_ID' => $application->PRO_ID - ]); - $title = $application->APP_TITLE; - } + $delegations = factory(Delegation::class, 1) + ->states('foreign_keys') + ->create(); + $title = $delegations->last() + ->application + ->APP_TITLE; // We need to commit the records inserted because is needed for the "fulltext" index DB::commit(); // Searching by a existent case title, result ordered by APP_NUMBER, filter by APP_NUMBER in DESC mode $results = Delegation::search(null, 0, 10, $title, null, null, 'DESC', - 'APP_NUMBER', null, null, null, 'APP_TITLE'); + 'APP_NUMBER', null, null, null, 'APP_TITLE'); $this->assertCount(1, $results['data']); $this->assertEquals($title, $results['data'][0]['APP_TITLE']); // Searching by a existent case title, result ordered by APP_NUMBER, filter by APP_NUMBER in ASC mode $results = Delegation::search(null, 0, 10, $title, null, null, 'ASC', - 'APP_NUMBER', null, null, null, 'APP_TITLE'); + 'APP_NUMBER', null, null, null, 'APP_TITLE'); $this->assertCount(1, $results['data']); $this->assertEquals($title, $results['data'][0]['APP_TITLE']); } @@ -536,7 +541,6 @@ class DelegationTest extends TestCase // Process with the category to search $category = factory(ProcessCategory::class)->create(); $processSearch = factory(Process::class)->create([ - 'PRO_ID' => 5, 'PRO_CATEGORY' => $category->CATEGORY_UID ]); // Delegations to found diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Model/ProcessTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Model/ProcessTest.php index 41af3a3d8..9168176e5 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Model/ProcessTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Model/ProcessTest.php @@ -25,10 +25,10 @@ class ProcessTest extends TestCase */ public function it_has_tasks() { - $process = factory(Process::class)->create([ - 'PRO_ID' => function () { - return factory(Task::class)->create()->PRO_ID; - } + $process = factory(Process::class)->create(); + factory(Task::class)->create([ + 'PRO_UID' => $process->PRO_UID, + 'PRO_ID' => $process->PRO_ID ]); $this->assertInstanceOf(Task::class, $process->tasks); } diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Services/OAuth2/ServerTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Services/OAuth2/ServerTest.php index d99f8885a..9684efc69 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Services/OAuth2/ServerTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Services/OAuth2/ServerTest.php @@ -192,11 +192,12 @@ class ServerTest extends TestCase if (!is_dir(PATH_DB)) { mkdir(PATH_DB); } - if (!is_dir(PATH_WORKSPACE)) { - mkdir(PATH_WORKSPACE); + $pathWorkspace = PATH_DB . config('system.workspace') . PATH_SEP; + if (!is_dir($pathWorkspace)) { + mkdir($pathWorkspace); } $info = $this->getLicenseInfo(); - $filename = PATH_WORKSPACE . $info['name']; + $filename = $pathWorkspace . $info['name']; $data = $info['content']; file_put_contents($filename, $data); diff --git a/workflow/engine/classes/Cases.php b/workflow/engine/classes/Cases.php index 2ad834515..fdeade4d8 100644 --- a/workflow/engine/classes/Cases.php +++ b/workflow/engine/classes/Cases.php @@ -2,6 +2,7 @@ use ProcessMaker\BusinessModel\User as BusinessModelUser; use ProcessMaker\BusinessModel\WebEntryEvent; +use ProcessMaker\Cases\CasesTrait; /*----------------------------------********---------------------------------*/ use ProcessMaker\ChangeLog\ChangeLog; /*----------------------------------********---------------------------------*/ @@ -16,6 +17,7 @@ use ProcessMaker\Util\DateTime; */ class Cases { + use CasesTrait; private $appSolr = null; public $dir = 'ASC'; public $sort = 'APP_MSG_DATE'; diff --git a/workflow/engine/classes/Derivation.php b/workflow/engine/classes/Derivation.php index 9e09638f5..49302cd53 100644 --- a/workflow/engine/classes/Derivation.php +++ b/workflow/engine/classes/Derivation.php @@ -888,7 +888,7 @@ class Derivation define( 'TASK_FINISH_TASK', - 2 ); } - $this->case = new cases(); + $this->case = new Cases(); //Get data for this DEL_INDEX current $appFields = $this->case->loadCase( $currentDelegation['APP_UID'], $currentDelegation['DEL_INDEX'] ); diff --git a/workflow/engine/classes/class.pmScript.php b/workflow/engine/classes/class.pmScript.php index 9beeca206..71b081dfa 100644 --- a/workflow/engine/classes/class.pmScript.php +++ b/workflow/engine/classes/class.pmScript.php @@ -2,9 +2,14 @@ use ProcessMaker\Plugins\PluginRegistry; -spl_autoload_register(function($sClassName) { - if (!empty(config("system.workspace"))) { - $sPath = PATH_DB . config("system.workspace") . PATH_SEP . 'classes' . PATH_SEP; +/** + * The helper 'config()' is loaded via 'spl_autoload_register()' in unit testing. + * The helper is pulled out to avoid an infinite loop. + */ +$workspace = config("system.workspace", null); +spl_autoload_register(function($sClassName) use($workspace) { + if (!empty($workspace)) { + $sPath = PATH_DB . $workspace . PATH_SEP . 'classes' . PATH_SEP; if (file_exists($sPath . $sClassName . '.php')) { require_once $sPath . $sClassName . '.php'; } diff --git a/workflow/engine/methods/cases/cases_Derivate.php b/workflow/engine/methods/cases/cases_Derivate.php index ce16237a7..5acfad207 100644 --- a/workflow/engine/methods/cases/cases_Derivate.php +++ b/workflow/engine/methods/cases/cases_Derivate.php @@ -1,37 +1,40 @@ - var olink = document.location.href; - olink = ( olink.search("gmail") == -1 ) ? parent.document.location.href : olink; - if(olink.search("gmail") == -1){ - parent.location = "../cases/casesStartPage?action=startCase"; - } else { - var data = olink.split("?"); - var odata = data[1].split("&"); - var appUid = odata[0].split("="); + $script = ' + '); + var dataToSend = { + "action": "credentials", + "operation": "refreshPmSession", + "type": "processCall", + "funParams": [ + appUid[1], + "" + ], + "expectReturn": false + }; + var x = parent.postMessage(JSON.stringify(dataToSend), "*"); + if (x == undefined){ + x = parent.parent.postMessage(JSON.stringify(dataToSend), "*"); + } + } + '; + die($script); } /* Permissions */ @@ -53,6 +56,7 @@ switch ($RBAC->userCanAccess('PM_CASES')) { if (!isset($_POST['form'])) { $_POST['form'] = []; } +$postForm = $_POST['form']; /* GET , POST & $_SESSION Vars */ /* Process the info */ @@ -61,15 +65,11 @@ $sStatus = 'TO_DO'; try { //Load Session variables $processUid = isset($_SESSION['PROCESS']) ? $_SESSION['PROCESS'] : ''; - //load data - $oCase = new Cases(); // check if a task was already derivated - if (isset($_SESSION["APPLICATION"]) - && isset($_SESSION["INDEX"])) { - $_SESSION['LAST_DERIVATED_APPLICATION'] = isset($_SESSION['LAST_DERIVATED_APPLICATION'])?$_SESSION['LAST_DERIVATED_APPLICATION']:''; - $_SESSION['LAST_DERIVATED_INDEX'] = isset($_SESSION['LAST_DERIVATED_INDEX'])?$_SESSION['LAST_DERIVATED_INDEX']:''; - if ($_SESSION["APPLICATION"] === $_SESSION['LAST_DERIVATED_APPLICATION'] - && $_SESSION["INDEX"] === $_SESSION['LAST_DERIVATED_INDEX']) { + if (isset($_SESSION["APPLICATION"]) && isset($_SESSION["INDEX"])) { + $_SESSION['LAST_DERIVATED_APPLICATION'] = isset($_SESSION['LAST_DERIVATED_APPLICATION']) ? $_SESSION['LAST_DERIVATED_APPLICATION'] : ''; + $_SESSION['LAST_DERIVATED_INDEX'] = isset($_SESSION['LAST_DERIVATED_INDEX']) ? $_SESSION['LAST_DERIVATED_INDEX'] : ''; + if ($_SESSION["APPLICATION"] === $_SESSION['LAST_DERIVATED_APPLICATION'] && $_SESSION["INDEX"] === $_SESSION['LAST_DERIVATED_INDEX']) { throw new Exception(G::LoadTranslation('ID_INVALID_APPLICATION_ID_MSG', [G::LoadTranslation('ID_REOPEN')])); } else { $appDel = new AppDelegation(); @@ -84,116 +84,6 @@ try { throw new Exception(G::LoadTranslation('ID_INVALID_APPLICATION_ID_MSG', [G::LoadTranslation('ID_REOPEN')])); } - //warning: we are not using the result value of function thisIsTheCurrentUser, so I'm commenting to optimize speed. - //$oCase->thisIsTheCurrentUser( $_SESSION['APPLICATION'], $_SESSION['INDEX'], $_SESSION['USER_LOGGED'], 'REDIRECT', 'casesListExtJs'); - $appFields = $oCase->loadCase($_SESSION['APPLICATION']); - $appFields['APP_DATA'] = array_merge($appFields['APP_DATA'], G::getSystemConstants()); - //cleaning debug variables - $_SESSION['TRIGGER_DEBUG']['DATA'] = []; - $_SESSION['TRIGGER_DEBUG']['TRIGGERS_NAMES'] = []; - $_SESSION['TRIGGER_DEBUG']['TRIGGERS_VALUES'] = []; - $_SESSION['TRIGGER_DEBUG']['TRIGGERS_EXECUTION_TIME'] = []; - - $triggers = $oCase->loadTriggers($_SESSION['TASK'], 'ASSIGN_TASK', -2, 'BEFORE'); - - //if there are some triggers to execute - if (sizeof($triggers) > 0) { - //Execute triggers before derivation - $appFields['APP_DATA'] = $oCase->ExecuteTriggers($_SESSION['TASK'], 'ASSIGN_TASK', -2, 'BEFORE', - $appFields['APP_DATA']); - - //save trigger variables for debugger - $_SESSION['TRIGGER_DEBUG']['info'][0]['NUM_TRIGGERS'] = sizeof($triggers); - $_SESSION['TRIGGER_DEBUG']['info'][0]['TIME'] = G::toUpper(G::loadTranslation('ID_BEFORE')); - $_SESSION['TRIGGER_DEBUG']['info'][0]['TRIGGERS_NAMES'] = array_column($triggers, 'TRI_TITLE'); - $_SESSION['TRIGGER_DEBUG']['info'][0]['TRIGGERS_VALUES'] = $triggers; - $_SESSION['TRIGGER_DEBUG']['info'][0]['TRIGGERS_EXECUTION_TIME'] = $oCase->arrayTriggerExecutionTime; - } - - unset($appFields['APP_STATUS']); - unset($appFields['APP_PROC_STATUS']); - unset($appFields['APP_PROC_CODE']); - unset($appFields['APP_PIN']); - - $appFields["DEL_INDEX"] = $_SESSION["INDEX"]; - $appFields["TAS_UID"] = $_SESSION["TASK"]; - $appFields["USER_UID"] = $_SESSION["USER_LOGGED"]; - $appFields["CURRENT_DYNAFORM"] = "-2"; - $appFields["OBJECT_TYPE"] = "ASSIGN_TASK"; - - $oCase->updateCase($_SESSION["APPLICATION"], $appFields); //Save data - - //Prepare information for the derivation - $oDerivation = new Derivation(); - $aCurrentDerivation = [ - 'APP_UID' => $_SESSION['APPLICATION'], - 'DEL_INDEX' => $_SESSION['INDEX'], - 'APP_STATUS' => $sStatus, - 'TAS_UID' => $_SESSION['TASK'], - 'ROU_TYPE' => $_POST['form']['ROU_TYPE'] - ]; - $aDataForPrepareInfo = [ - 'USER_UID' => $_SESSION['USER_LOGGED'], - 'APP_UID' => $_SESSION['APPLICATION'], - 'DEL_INDEX' => $_SESSION['INDEX'] - ]; - - //We define some parameters in the before the derivation - //Then this function will be route the case - $arrayDerivationResult = $oDerivation->beforeDerivate( - $aDataForPrepareInfo, - $_POST['form']['TASKS'], - $_POST['form']['ROU_TYPE'], - $aCurrentDerivation - ); - - if (!empty($arrayDerivationResult)) { - foreach ($_POST['form']['TASKS'] as $key => $value) { - if (isset($value['TAS_UID'])) { - foreach ($arrayDerivationResult as $value2) { - if ($value2['TAS_UID'] == $value['TAS_UID']) { - $_POST['form']['TASKS'][$key]['DEL_INDEX'] = $value2['DEL_INDEX']; - break; - } - } - } - } - } - - $appFields = $oCase->loadCase($_SESSION['APPLICATION']); //refresh appFields, because in derivations should change some values - $triggers = $oCase->loadTriggers($_SESSION['TASK'], 'ASSIGN_TASK', -2, - 'AFTER'); //load the triggers after derivation - if (sizeof($triggers) > 0) { - $appFields['APP_DATA'] = $oCase->ExecuteTriggers($_SESSION['TASK'], 'ASSIGN_TASK', -2, 'AFTER', - $appFields['APP_DATA']); //Execute triggers after derivation - - - $_SESSION['TRIGGER_DEBUG']['info'][1]['NUM_TRIGGERS'] = sizeof($triggers); - $_SESSION['TRIGGER_DEBUG']['info'][1]['TIME'] = G::toUpper(G::loadTranslation('ID_AFTER')); - $_SESSION['TRIGGER_DEBUG']['info'][1]['TRIGGERS_NAMES'] = array_column($triggers, 'TRI_TITLE'); - $_SESSION['TRIGGER_DEBUG']['info'][1]['TRIGGERS_VALUES'] = $triggers; - $_SESSION['TRIGGER_DEBUG']['info'][1]['TRIGGERS_EXECUTION_TIME'] = $oCase->arrayTriggerExecutionTime; - } - unset($appFields['APP_STATUS']); - unset($appFields['APP_PROC_STATUS']); - unset($appFields['APP_PROC_CODE']); - unset($appFields['APP_PIN']); - - $appFields["DEL_INDEX"] = $_SESSION["INDEX"]; - $appFields["TAS_UID"] = $_SESSION["TASK"]; - $appFields["USER_UID"] = $_SESSION["USER_LOGGED"]; - $appFields["CURRENT_DYNAFORM"] = "-2"; - $appFields["OBJECT_TYPE"] = "ASSIGN_TASK"; - - $oCase->updateCase($_SESSION['APPLICATION'], $appFields); - - // Send notifications - Start - $oUser = new Users(); - $aUser = $oUser->load($_SESSION['USER_LOGGED']); - $fromName = $aUser['USR_FIRSTNAME'] . ' ' . $aUser['USR_LASTNAME']; - - $sFromData = $fromName . ($aUser['USR_EMAIL'] != '' ? ' <' . $aUser['USR_EMAIL'] . '>' : ''); - $flagGmail = false; /*----------------------------------********---------------------------------*/ $licensedFeatures = PMLicensedFeatures::getSingleton(); @@ -201,80 +91,29 @@ try { $pmGoogle = new PmGoogleApi(); if ($pmGoogle->getServiceGmailStatus()) { $flagGmail = true; - - $appDel = new AppDelegation(); - $actualThread = $appDel->Load($_SESSION ['APPLICATION'], $_SESSION ['INDEX']); - - $appDelPrev = $appDel->LoadParallel($_SESSION ['APPLICATION']); - $Pmgmail = new \ProcessMaker\BusinessModel\Pmgmail(); - foreach ($appDelPrev as $app) { - if (($app ['DEL_INDEX'] != $_SESSION ['INDEX']) && ($app ['DEL_PREVIOUS'] != $actualThread ['DEL_PREVIOUS'])) { - $Pmgmail->gmailsIfSelfServiceValueBased($_SESSION ['APPLICATION'], $app ['DEL_INDEX'], $_POST ['form'] ['TASKS'], $appFields ['APP_DATA']); - } - } } } /*----------------------------------********---------------------------------*/ - try { - $oCase->sendNotifications( - $_SESSION['TASK'], - $_POST['form']['TASKS'], - $appFields['APP_DATA'], - $_SESSION['APPLICATION'], - $_SESSION['INDEX'], - $sFromData - ); - } catch (Exception $e) { - G::SendTemporalMessage(G::loadTranslation('ID_NOTIFICATION_ERROR') . ' - ' . $e->getMessage(), 'warning', - 'string', null, '100%'); - } - // Send notifications - End - // Events - Start - $oEvent = new Event(); - - $oEvent->closeAppEvents($processUid, $_SESSION['APPLICATION'], $_SESSION['INDEX'], $_SESSION['TASK']); - $oCurrentAppDel = AppDelegationPeer::retrieveByPk($_SESSION['APPLICATION'], $_SESSION['INDEX'] + 1); - $multipleDelegation = false; - // check if there are multiple derivations - if (count($_POST['form']['TASKS']) > 1) { - $multipleDelegation = true; - } - // If the case has been delegated - if (isset($oCurrentAppDel)) { - // if there is just a single derivation the TASK_UID can be set by the delegation data - if (!$multipleDelegation) { - $aCurrentAppDel = $oCurrentAppDel->toArray(BasePeer::TYPE_FIELDNAME); - $oEvent->createAppEvents($aCurrentAppDel['PRO_UID'], $aCurrentAppDel['APP_UID'], - $aCurrentAppDel['DEL_INDEX'], $aCurrentAppDel['TAS_UID']); - } else { - // else we need to check every task and create the events if it have any - foreach ($_POST['form']['TASKS'] as $taskDelegated) { - $aCurrentAppDel = $oCurrentAppDel->toArray(BasePeer::TYPE_FIELDNAME); - $oEvent->createAppEvents($aCurrentAppDel['PRO_UID'], $aCurrentAppDel['APP_UID'], - $aCurrentAppDel['DEL_INDEX'], $taskDelegated['TAS_UID']); - } - } - } - //Events - End - - /*----------------------------------********---------------------------------*/ - // Set users drive - start - $licensedFeatures = PMLicensedFeatures::getSingleton(); - if ($licensedFeatures->verifyfeature('AhKNjBEVXZlWUFpWE8wVTREQ0FObmo0aTdhVzhvalFic1M=')) { - $drive = new AppDocumentDrive(); - if ($drive->getStatusDrive()) { - //add users email next task - $drive->addUsersDocumentDrive($appFields['APP_UID']); - } - } - // Set users drive - End - /*----------------------------------********---------------------------------*/ + $application = $_SESSION['APPLICATION']; + $tasUid = $_SESSION['TASK']; + $index = $_SESSION["INDEX"]; + $userLogged = $_SESSION["USER_LOGGED"]; + + //Now we dispatch the derivation of the case through Jobs Laravel. + $closure = function() use($processUid, $application, $postForm, $sStatus, $flagGmail, $tasUid, $index, $userLogged) { + $cases = new Cases(); + $cases->routeCase($processUid, $application, $postForm, $sStatus, $flagGmail, $tasUid, $index, $userLogged); + }; + JobsManager::getSingleton()->dispatch("CasesDispatch", $closure); + + //We close the related threads. + $cases = new Cases(); + $cases->CloseCurrentDelegation($application, $index); $debuggerAvailable = true; - $casesRedirector = 'casesListExtJsRedirector'; - if (isset ($_SESSION ['user_experience']) && $flagGmail === false) { + if (isset($_SESSION ['user_experience']) && $flagGmail === false) { $aNextStep ['PAGE'] = $casesRedirector . '?ux=' . $_SESSION ['user_experience']; $debuggerAvailable = false; } else { @@ -285,43 +124,24 @@ try { } } - if (isset($_SESSION['PMDEBUGGER']) && $_SESSION['PMDEBUGGER'] && $debuggerAvailable) { - $_SESSION['TRIGGER_DEBUG']['BREAKPAGE'] = $aNextStep['PAGE']; - $loc = 'cases_Step?' . 'breakpoint=triggerdebug'; - } else { - $loc = $aNextStep['PAGE']; - } + $loc = $aNextStep['PAGE']; + //Triggers After $isIE = Bootstrap::isIE(); - - if (isset($_SESSION['TRIGGER_DEBUG']['ISSET']) && !$isIE) { - if ($_SESSION['TRIGGER_DEBUG']['ISSET'] == 1) { - $oTemplatePower = new TemplatePower(PATH_TPL . 'cases/cases_Step.html'); - $oTemplatePower->prepare(); - $G_PUBLISH = new Publisher(); - $G_PUBLISH->AddContent('template', '', '', '', $oTemplatePower); - $_POST['NextStep'] = $loc; - $G_PUBLISH->AddContent('view', 'cases/showDebugFrameLoader'); - $G_PUBLISH->AddContent('view', 'cases/showDebugFrameBreaker'); - $_SESSION['TRIGGER_DEBUG']['ISSET'] == 0; - G::RenderPage('publish', 'blank'); - exit(); - } else { - unset($_SESSION['TRIGGER_DEBUG']); - } - } + unset($_SESSION['TRIGGER_DEBUG']); //close tab only if IE11 add a validation was added if the current skin is uxs if ($isIE && !isset($_SESSION['__OUTLOOK_CONNECTOR__']) && SYS_SKIN !== "uxs") { - $script = ""; + $script = " + "; die($script); } diff --git a/workflow/engine/src/ProcessMaker/Cases/CasesTrait.php b/workflow/engine/src/ProcessMaker/Cases/CasesTrait.php new file mode 100644 index 000000000..d7d35bdf3 --- /dev/null +++ b/workflow/engine/src/ProcessMaker/Cases/CasesTrait.php @@ -0,0 +1,205 @@ +loadCase($application); + $appFields['APP_DATA'] = array_merge($appFields['APP_DATA'], G::getSystemConstants()); + + $triggerDebug = []; + $triggers = $this->loadTriggers($tasUid, 'ASSIGN_TASK', -2, 'BEFORE'); + + //if there are some triggers to execute + if (sizeof($triggers) > 0) { + //Execute triggers before derivation + $appFields['APP_DATA'] = $this->executeTriggers($tasUid, 'ASSIGN_TASK', -2, 'BEFORE', $appFields['APP_DATA']); + + //save trigger variables for debugger + $triggerDebug[] = [ + 'NUM_TRIGGERS' => sizeof($triggers), + 'TIME' => G::toUpper(G::loadTranslation('ID_BEFORE')), + 'TRIGGERS_NAMES' => array_column($triggers, 'TRI_TITLE'), + 'TRIGGERS_VALUES' => $triggers, + 'TRIGGERS_EXECUTION_TIME' => $this->arrayTriggerExecutionTime + ]; + } + + unset($appFields['APP_STATUS']); + unset($appFields['APP_PROC_STATUS']); + unset($appFields['APP_PROC_CODE']); + unset($appFields['APP_PIN']); + + $appFields["DEL_INDEX"] = $index; + $appFields["TAS_UID"] = $tasUid; + $appFields["USER_UID"] = $userLogged; + $appFields["CURRENT_DYNAFORM"] = "-2"; + $appFields["OBJECT_TYPE"] = "ASSIGN_TASK"; + + //save data + $this->updateCase($application, $appFields); + + //prepare information for the derivation + $derivation = new Derivation(); + $currentDerivation = [ + 'APP_UID' => $application, + 'DEL_INDEX' => $index, + 'APP_STATUS' => $status, + 'TAS_UID' => $tasUid, + 'ROU_TYPE' => $postForm['ROU_TYPE'] + ]; + $dataForPrepareInfo = [ + 'USER_UID' => $userLogged, + 'APP_UID' => $application, + 'DEL_INDEX' => $index + ]; + + //we define some parameters in the before the derivation + //then this function will be route the case + $arrayDerivationResult = $derivation->beforeDerivate( + $dataForPrepareInfo, + $postForm['TASKS'], + $postForm['ROU_TYPE'], + $currentDerivation + ); + + if (!empty($arrayDerivationResult)) { + foreach ($postForm['TASKS'] as $key => $value) { + if (isset($value['TAS_UID'])) { + foreach ($arrayDerivationResult as $value2) { + if ($value2['TAS_UID'] == $value['TAS_UID']) { + $postForm['TASKS'][$key]['DEL_INDEX'] = $value2['DEL_INDEX']; + break; + } + } + } + } + } + + $appFields = $this->loadCase($application); //refresh appFields, because in derivations should change some values + $triggers = $this->loadTriggers($tasUid, 'ASSIGN_TASK', -2, 'AFTER'); //load the triggers after derivation + if (sizeof($triggers) > 0) { + $appFields['APP_DATA'] = $this->ExecuteTriggers($tasUid, 'ASSIGN_TASK', -2, 'AFTER', $appFields['APP_DATA']); //Execute triggers after derivation + + $triggerDebug[] = [ + 'NUM_TRIGGERS' => sizeof($triggers), + 'TIME' => G::toUpper(G::loadTranslation('ID_AFTER')), + 'TRIGGERS_NAMES' => array_column($triggers, 'TRI_TITLE'), + 'TRIGGERS_VALUES' => $triggers, + 'TRIGGERS_EXECUTION_TIME' => $this->arrayTriggerExecutionTime + ]; + } + unset($appFields['APP_STATUS']); + unset($appFields['APP_PROC_STATUS']); + unset($appFields['APP_PROC_CODE']); + unset($appFields['APP_PIN']); + + $appFields["DEL_INDEX"] = $index; + $appFields["TAS_UID"] = $tasUid; + $appFields["USER_UID"] = $userLogged; + $appFields["CURRENT_DYNAFORM"] = "-2"; + $appFields["OBJECT_TYPE"] = "ASSIGN_TASK"; + + $this->updateCase($application, $appFields); + + // Send notifications - Start + $oUser = new Users(); + $aUser = $oUser->load($userLogged); + $fromName = $aUser['USR_FIRSTNAME'] . ' ' . $aUser['USR_LASTNAME']; + + $sFromData = $fromName . ($aUser['USR_EMAIL'] != '' ? ' <' . $aUser['USR_EMAIL'] . '>' : ''); + + if ($flagGmail === true) { + $appDel = new AppDelegation(); + $actualThread = $appDel->Load($application, $index); + + $appDelPrev = $appDel->LoadParallel($application); + $Pmgmail = new Pmgmail(); + foreach ($appDelPrev as $app) { + if (($app['DEL_INDEX'] != $index) && ($app['DEL_PREVIOUS'] != $actualThread['DEL_PREVIOUS'])) { + $Pmgmail->gmailsIfSelfServiceValueBased($application, $app['DEL_INDEX'], $postForm['TASKS'], $appFields['APP_DATA']); + } + } + } + + try { + $this->sendNotifications($tasUid, $postForm['TASKS'], $appFields['APP_DATA'], $application, $index, $sFromData); + } catch (Exception $e) { + G::SendTemporalMessage(G::loadTranslation('ID_NOTIFICATION_ERROR') . ' - ' . $e->getMessage(), 'warning', 'string', null, '100%'); + } + // Send notifications - End + // Events - Start + $event = new Event(); + + $event->closeAppEvents($processUid, $application, $index, $tasUid); + $currentAppDel = AppDelegationPeer::retrieveByPk($application, $index + 1); + $multipleDelegation = false; + // check if there are multiple derivations + if (count($postForm['TASKS']) > 1) { + $multipleDelegation = true; + } + // If the case has been delegated + if (isset($currentAppDel)) { + // if there is just a single derivation the TASK_UID can be set by the delegation data + if (!$multipleDelegation) { + $arrayResult = $currentAppDel->toArray(BasePeer::TYPE_FIELDNAME); + $event->createAppEvents($arrayResult['PRO_UID'], $arrayResult['APP_UID'], $arrayResult['DEL_INDEX'], $arrayResult['TAS_UID']); + } else { + // else we need to check every task and create the events if it have any + foreach ($postForm['TASKS'] as $taskDelegated) { + $arrayResult = $currentAppDel->toArray(BasePeer::TYPE_FIELDNAME); + $event->createAppEvents($arrayResult['PRO_UID'], $arrayResult['APP_UID'], $arrayResult['DEL_INDEX'], $taskDelegated['TAS_UID']); + } + } + } + //Events - End + + /*----------------------------------********---------------------------------*/ + // Set users drive - start + $licensedFeatures = PMLicensedFeatures::getSingleton(); + if ($licensedFeatures->verifyfeature('AhKNjBEVXZlWUFpWE8wVTREQ0FObmo0aTdhVzhvalFic1M=')) { + $drive = new AppDocumentDrive(); + if ($drive->getStatusDrive()) { + //add users email next task + $drive->addUsersDocumentDrive($appFields['APP_UID']); + } + } + // Set users drive - End + /*----------------------------------********---------------------------------*/ + + $result = [ + 'appFields' => $appFields, + 'triggerDebug' => $triggerDebug + ]; + return (object) $result; + } +} diff --git a/workflow/engine/src/ProcessMaker/Model/StepTrigger.php b/workflow/engine/src/ProcessMaker/Model/StepTrigger.php new file mode 100644 index 000000000..5285cd179 --- /dev/null +++ b/workflow/engine/src/ProcessMaker/Model/StepTrigger.php @@ -0,0 +1,14 @@ + Date: Mon, 25 May 2020 15:15:31 -0400 Subject: [PATCH 02/24] PMCORE-1402 Use Jobs in the email execution related to the 'Action by email' --- app/Jobs/ActionByEmail.php | 14 ++ app/Jobs/CasesDispatch.php | 8 - app/Jobs/QueuedClosure.php | 10 ++ app/Jobs/RouteCase.php | 14 ++ .../factories/AbeConfigurationFactory.php | 14 +- database/factories/AbeRequestFactory.php | 11 +- database/factories/DynaformFactory.php | 13 +- database/factories/InputDocumentFactory.php | 1 - .../src/ProcessMaker/Cases/CasesTraitTest.php | 166 +++++++++++++++++- .../src/ProcessMaker/Core/JobsManagerTest.php | 2 +- workflow/engine/classes/WsBase.php | 3 +- .../engine/methods/cases/cases_Derivate.php | 5 +- .../services/ActionsByEmailDataFormPost.php | 128 +++----------- .../BusinessModel/Factories/Jobs.php | 43 ----- .../src/ProcessMaker/Cases/CasesTrait.php | 135 +++++++++++++- .../src/ProcessMaker/Core/JobsManager.php | 3 +- 16 files changed, 385 insertions(+), 185 deletions(-) create mode 100644 app/Jobs/ActionByEmail.php delete mode 100644 app/Jobs/CasesDispatch.php create mode 100644 app/Jobs/RouteCase.php delete mode 100644 workflow/engine/src/ProcessMaker/BusinessModel/Factories/Jobs.php diff --git a/app/Jobs/ActionByEmail.php b/app/Jobs/ActionByEmail.php new file mode 100644 index 000000000..0ae7b8a42 --- /dev/null +++ b/app/Jobs/ActionByEmail.php @@ -0,0 +1,14 @@ +define(\ProcessMaker\Model\AbeConfiguration::class, function (Faker $faker) { - $process = \ProcessMaker\Model\Process::all()->random(); - $task = \ProcessMaker\Model\Task::all()->random(); - $dynaForm = \ProcessMaker\Model\Dynaform::all()->random(); - $emailServer = \ProcessMaker\Model\EmailServerModel::all()->random(); + $process = factory(\ProcessMaker\Model\Process::class)->create(); + $dynaform = factory(\ProcessMaker\Model\Dynaform::class)->create([ + 'PRO_UID' => $process->PRO_UID + ]); + $task = factory(\ProcessMaker\Model\Task::class)->create([ + 'PRO_UID' => $process->PRO_UID + ]); + $emailServer = factory(\ProcessMaker\Model\EmailServerModel::class)->create(); return [ 'ABE_UID' => G::generateUniqueID(), 'PRO_UID' => $process->PRO_UID, @@ -14,7 +18,7 @@ $factory->define(\ProcessMaker\Model\AbeConfiguration::class, function (Faker $f 'ABE_TYPE' => $faker->randomElement(['', 'LINK']), 'ABE_TEMPLATE' => 'actionByEmail.html', 'ABE_DYN_TYPE' => 'NORMAL', - 'DYN_UID' => $dynaForm->DYN_UID, + 'DYN_UID' => $dynaform->DYN_UID, 'ABE_EMAIL_FIELD' => 'admin@processmaker.com', 'ABE_ACTION_FIELD' => '', 'ABE_CASE_NOTE_IN_RESPONSE' => $faker->randomElement(['0', '1']), diff --git a/database/factories/AbeRequestFactory.php b/database/factories/AbeRequestFactory.php index 9c2383fae..387f10357 100644 --- a/database/factories/AbeRequestFactory.php +++ b/database/factories/AbeRequestFactory.php @@ -3,12 +3,17 @@ use Faker\Generator as Faker; $factory->define(\ProcessMaker\Model\AbeRequest::class, function (Faker $faker) { - $process = \ProcessMaker\Model\Application::all()->random(); - $abeConfiguration = \ProcessMaker\Model\AbeConfiguration::all()->random(); + $process = factory(\ProcessMaker\Model\Process::class)->create(); + $abeConfiguration = factory(\ProcessMaker\Model\AbeConfiguration::class)->create([ + 'PRO_UID' => $process->PRO_UID + ]); + $application = factory(\ProcessMaker\Model\Application::class)->create([ + 'PRO_UID' => $process->PRO_UID + ]); return [ 'ABE_REQ_UID' => G::generateUniqueID(), 'ABE_UID' => $abeConfiguration->ABE_UID, - 'APP_UID' => $process->APP_UID, + 'APP_UID' => $application->APP_UID, 'DEL_INDEX' => 0, 'ABE_REQ_SENT_TO' => $faker->email, 'ABE_REQ_SUBJECT' => '', diff --git a/database/factories/DynaformFactory.php b/database/factories/DynaformFactory.php index 428b5d28c..793d743d7 100644 --- a/database/factories/DynaformFactory.php +++ b/database/factories/DynaformFactory.php @@ -10,10 +10,12 @@ $factory->define(\ProcessMaker\Model\Dynaform::class, function(Faker $faker) { $date = $faker->dateTime(); return [ 'DYN_UID' => G::generateUniqueID(), - 'DYN_ID' => $faker->unique()->numberBetween(1, 10000), 'DYN_TITLE' => $faker->sentence(2), 'DYN_DESCRIPTION' => $faker->sentence(5), - 'PRO_UID' => G::generateUniqueID(), + 'PRO_UID' => function() { + $process = factory(Process::class)->create(); + return $process->PRO_UID; + }, 'DYN_TYPE' => 'xmlform', 'DYN_FILENAME' => '', 'DYN_CONTENT' => '', @@ -27,7 +29,6 @@ $factory->state(\ProcessMaker\Model\Dynaform::class, 'foreign_keys', function (F $date = $faker->dateTime(); return [ 'DYN_UID' => G::generateUniqueID(), - 'DYN_ID' => $faker->unique()->numberBetween(1, 10000), 'DYN_TITLE' => $faker->sentence(2), 'DYN_DESCRIPTION' => $faker->sentence(5), 'PRO_UID' => function() { @@ -49,10 +50,12 @@ $factory->state(\ProcessMaker\Model\Dynaform::class, 'translations', function (F $date = $faker->dateTime(); return [ 'DYN_UID' => G::generateUniqueID(), - 'DYN_ID' => $faker->unique()->numberBetween(1, 10000), 'DYN_TITLE' => $faker->sentence(2), 'DYN_DESCRIPTION' => $faker->sentence(5), - 'PRO_UID' => G::generateUniqueID(), + 'PRO_UID' => function() { + $process = factory(Process::class)->create(); + return $process->PRO_UID; + }, 'DYN_TYPE' => 'xmlform', 'DYN_FILENAME' => '', 'DYN_CONTENT' => '', diff --git a/database/factories/InputDocumentFactory.php b/database/factories/InputDocumentFactory.php index 54481b00b..5d2200275 100644 --- a/database/factories/InputDocumentFactory.php +++ b/database/factories/InputDocumentFactory.php @@ -10,7 +10,6 @@ use ProcessMaker\Model\Process; $factory->define(InputDocument::class, function(Faker $faker) { return [ 'INP_DOC_UID' => G::generateUniqueID(), - 'INP_DOC_ID' => $faker->unique()->numberBetween(1, 10000), 'PRO_UID' => function() { $process = factory(Process::class)->create(); return $process->PRO_UID; diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Cases/CasesTraitTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Cases/CasesTraitTest.php index 60a064336..8b2a84eda 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Cases/CasesTraitTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Cases/CasesTraitTest.php @@ -2,11 +2,17 @@ namespace Tests\unit\workflow\engine\src\ProcessMaker\Cases; -use App\Jobs\CasesDispatch; +use App\Jobs\RouteCase; use Cases; +use G; use Illuminate\Support\Facades\Queue; +use ProcessMaker\Model\AbeConfiguration; +use ProcessMaker\Model\AbeRequest; use ProcessMaker\Model\Application; use ProcessMaker\Model\Delegation; +use ProcessMaker\Model\Dynaform; +use ProcessMaker\Model\EmailServerModel; +use ProcessMaker\Model\InputDocument; use ProcessMaker\Model\Process; use ProcessMaker\Model\Route; use ProcessMaker\Model\Step; @@ -286,6 +292,162 @@ class CasesTraitTest extends TestCase require_once PATH_METHODS . 'cases/cases_Derivate.php'; - Queue::assertPushed(CasesDispatch::class); + Queue::assertPushed(RouteCase::class); + } + + /** + * This test verifies if ABE is completed. + * @test + * @covers Cases::routeCaseActionByEmail + */ + public function it_should_verify_if_abe_is_completed() + { + $user = User::where('USR_ID', '=', 1)->get()->first(); + + $process = factory(Process::class)->create([ + 'PRO_CREATE_USER' => $user->USR_UID + ]); + $dynaform = factory(Dynaform::class)->create([ + 'PRO_UID' => $process->PRO_UID + ]); + $inpuDocument = factory(InputDocument::class)->create([ + 'PRO_UID' => $process->PRO_UID + ]); + $task = factory(Task::class)->create([ + 'TAS_ASSIGN_TYPE' => 'BALANCED', + 'TAS_GROUP_VARIABLE' => '', + 'PRO_UID' => $process->PRO_UID + ]); + factory(TaskUser::class)->create([ + 'TAS_UID' => $task->TAS_UID, + 'USR_UID' => $user->USR_UID, + 'TU_RELATION' => 1, + 'TU_TYPE' => 1 + ]); + $task2 = factory(Task::class)->create([ + 'TAS_ASSIGN_TYPE' => 'BALANCED', + 'TAS_GROUP_VARIABLE' => '', + 'PRO_UID' => $process->PRO_UID + ]); + factory(TaskUser::class)->create([ + 'TAS_UID' => $task2->TAS_UID, + 'USR_UID' => $user->USR_UID, + 'TU_RELATION' => 1, + 'TU_TYPE' => 1 + ]); + + $application = factory(Application::class)->create([ + 'PRO_UID' => $process->PRO_UID + ]); + $delegation1 = factory(Delegation::class)->create([ + 'USR_UID' => $user->USR_UID, + 'PRO_UID' => $process->PRO_UID, + 'APP_UID' => $application->APP_UID, + 'TAS_UID' => $task->TAS_UID, + 'DEL_INDEX' => 1, + ]); + factory(Delegation::class)->create([ + 'USR_UID' => $user->USR_UID, + 'PRO_UID' => $process->PRO_UID, + 'APP_UID' => $application->APP_UID, + 'TAS_UID' => $task2->TAS_UID, + 'DEL_INDEX' => 2, + 'DEL_PREVIOUS' => $delegation1->DEL_INDEX + ]); + factory(Route::class)->create([ + 'TAS_UID' => $task->TAS_UID, + 'ROU_NEXT_TASK' => $task2->TAS_UID, + 'PRO_UID' => $process->PRO_UID + ]); + + $emailServer = factory(EmailServerModel::class)->create(); + $abeConfiguration = factory(AbeConfiguration::class)->create([ + 'PRO_UID' => $process->PRO_UID, + 'DYN_UID' => $dynaform->DYN_UID, + 'TAS_UID' => $task2->TAS_UID, + 'ABE_EMAIL_SERVER_UID' => $emailServer->MESS_UID, + 'ABE_TYPE' => 'LINK', + 'ABE_CASE_NOTE_IN_RESPONSE' => 1, + ]); + $abeRequest = factory(AbeRequest::class)->create([ + 'ABE_UID' => $abeConfiguration->ABE_UID, + 'APP_UID' => $application->APP_UID, + 'DEL_INDEX' => $delegation1->DEL_INDEX, + ]); + if (!defined('PATH_DOCUMENT')) { + define('PATH_DOCUMENT', PATH_DB . config('system.workspace') . PATH_SEP . 'files' . PATH_SEP); + } + + + $appUid = $delegation1->APP_UID; + $delIndex = $delegation1->DEL_INDEX; + $aber = $abeRequest->ABE_REQ_UID; + $dynUid = $dynaform->DYN_UID; + $forms = []; + $remoteAddr = '127.0.0.1'; + $files = [ + 'form' => [ + 'name' => ['test'], + 'type' => ['test'], + 'size' => ['1000'], + 'tmp_name' => [tempnam(sys_get_temp_dir(), 'test')], + 'error' => [''], + ] + ]; + + $cases = new Cases(); + $cases->routeCaseActionByEmail($appUid, $delIndex, $aber, $dynUid, $forms, $remoteAddr, $files); + } + + /** + * This test verifies if the ABE form has been completed. + * @test + * @covers Cases::routeCaseActionByEmail + */ + public function it_should_verify_if_abe_has_completed() + { + $delegation1 = factory(Delegation::class)->state('closed')->create(); + $abeRequest = factory(AbeRequest::class)->create(); + $dynaform = factory(Dynaform::class)->create([ + 'PRO_UID' => $delegation1->PRO_UID + ]); + + $appUid = $delegation1->APP_UID; + $delIndex = $delegation1->DEL_INDEX; + $aber = $abeRequest->ABE_REQ_UID; + $dynUid = $dynaform->DYN_UID; + $forms = []; + $remoteAddr = '127.0.0.1'; + $files = []; + + $this->expectException(\Exception::class); + $cases = new Cases(); + $cases->routeCaseActionByEmail($appUid, $delIndex, $aber, $dynUid, $forms, $remoteAddr, $files); + } + + /** + * This test verifies if the case has failed due to any circumstance. + * @test + * @covers Cases::routeCaseActionByEmail + */ + public function it_should_test_an_exception_if_the_case_throws_an_incorrect_state() + { + $delegation1 = factory(Delegation::class)->create(); + $abeRequest = factory(AbeRequest::class)->create(); + $dynaform = factory(Dynaform::class)->create([ + 'PRO_UID' => $delegation1->PRO_UID + ]); + + $appUid = $delegation1->APP_UID; + $delIndex = $delegation1->DEL_INDEX; + $aber = $abeRequest->ABE_REQ_UID; + $dynUid = $dynaform->DYN_UID; + $forms = []; + $remoteAddr = '127.0.0.1'; + $files = []; + + $this->expectException(\Exception::class); + $cases = new Cases(); + $cases->routeCaseActionByEmail($appUid, $delIndex, $aber, $dynUid, $forms, $remoteAddr, $files); } } diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Core/JobsManagerTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Core/JobsManagerTest.php index 423c722c7..5b2f5d5cd 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Core/JobsManagerTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Core/JobsManagerTest.php @@ -116,7 +116,7 @@ class JobsManagerTest extends TestCase $callback = function() { }; - $actual = $this->object->dispatch('Email', $callback); + $actual = $this->object->dispatch(\App\Jobs\Email::class, $callback); $this->assertInstanceOf(\Illuminate\Foundation\Bus\PendingDispatch::class, $actual); } diff --git a/workflow/engine/classes/WsBase.php b/workflow/engine/classes/WsBase.php index 898b2af56..ef1bb9352 100644 --- a/workflow/engine/classes/WsBase.php +++ b/workflow/engine/classes/WsBase.php @@ -1,5 +1,6 @@ dispatch('EmailEvent', $closure); + JobsManager::getSingleton()->dispatch(EmailEvent::class, $closure); break; default : $spool = $closure(); diff --git a/workflow/engine/methods/cases/cases_Derivate.php b/workflow/engine/methods/cases/cases_Derivate.php index 5acfad207..3d16da2c8 100644 --- a/workflow/engine/methods/cases/cases_Derivate.php +++ b/workflow/engine/methods/cases/cases_Derivate.php @@ -1,5 +1,6 @@ routeCase($processUid, $application, $postForm, $sStatus, $flagGmail, $tasUid, $index, $userLogged); }; - JobsManager::getSingleton()->dispatch("CasesDispatch", $closure); - + JobsManager::getSingleton()->dispatch(RouteCase::class, $closure); + //We close the related threads. $cases = new Cases(); $cases->CloseCurrentDelegation($application, $index); diff --git a/workflow/engine/methods/services/ActionsByEmailDataFormPost.php b/workflow/engine/methods/services/ActionsByEmailDataFormPost.php index 27d8c3502..71dfe8492 100644 --- a/workflow/engine/methods/services/ActionsByEmailDataFormPost.php +++ b/workflow/engine/methods/services/ActionsByEmailDataFormPost.php @@ -4,14 +4,14 @@ * @see workflow/engine/methods/services/ActionsByEmailDataForm.php * @link https://wiki.processmaker.com/3.3/Actions_by_Email#Link_to_Fill_a_Form */ - -use ProcessMaker\BusinessModel\Cases\InputDocument; -use ProcessMaker\ChangeLog\ChangeLog; +use App\Jobs\ActionByEmail; +use ProcessMaker\Core\JobsManager; use ProcessMaker\Validation\ValidationUploadedFiles; -if (PMLicensedFeatures::getSingleton() - ->verifyfeature('zLhSk5TeEQrNFI2RXFEVktyUGpnczV1WEJNWVp6cjYxbTU3R29mVXVZNWhZQT0=')) { - +$featureEnable = PMLicensedFeatures::getSingleton() + ->verifyfeature('zLhSk5TeEQrNFI2RXFEVktyUGpnczV1WEJNWVp6cjYxbTU3R29mVXVZNWhZQT0='); +if ($featureEnable) { + /** * To do: The following evaluation must be moved after saving the data (so as not to lose the data entered in the form). * It only remains because it is an old behavior, which must be defined by "Product Owner". @@ -22,7 +22,7 @@ if (PMLicensedFeatures::getSingleton() G::SendMessageText($validator->getMessage(), "ERROR"); $url = explode("sys" . config("system.workspace"), $_SERVER['HTTP_REFERER']); G::header("location: " . "/sys" . config("system.workspace") . $url[1]); - die(); + return; } $G_PUBLISH = new Publisher(); @@ -53,111 +53,23 @@ if (PMLicensedFeatures::getSingleton() $aber = G::decrypt($_REQUEST['ABER'], URL_KEY); $dynUid = G::decrypt($_REQUEST['DYN_UID'], URL_KEY); $forms = isset($_REQUEST['form']) ? $_REQUEST['form'] : []; + $remoteAddr = $_SERVER['REMOTE_ADDR']; + $files = $_FILES; - //Load data related to the case - $case = new Cases(); - $casesFields = $case->loadCase($appUid, $delIndex); - - // Check if the current thread is not finished - if (!is_null($casesFields['DEL_FINISH_DATE'])) { - throw new Exception(G::loadTranslation('ID_ABE_FORM_ALREADY_FILLED')); - } - // Merge the data - $casesFields['APP_DATA'] = array_merge($casesFields['APP_DATA'], $forms); - - //Get current user info - $delegation = new AppDelegation(); - $currentUsrUid = $delegation->getUserAssignedInThread($appUid, $delIndex); - if (!is_null($currentUsrUid)) { - $users = new Users(); - $userInfo = $users->loadDetails($currentUsrUid); - $casesFields["APP_DATA"]["USER_LOGGED"] = $currentUsrUid; - $casesFields["APP_DATA"]["USR_USERNAME"] = $userInfo['USR_USERNAME']; - } - - foreach ($casesFields["APP_DATA"] as $index => $value) { - $_SESSION[$index] = $value; - } - - $casesFields['CURRENT_DYNAFORM'] = $dynUid; - $casesFields['USER_UID'] = $casesFields['CURRENT_USER_UID']; - - ChangeLog::getChangeLog() - ->getUsrIdByUsrUid($casesFields['USER_UID'], true) - ->setSourceId(ChangeLog::FromABE); - - //Update case info - $case->updateCase($appUid, $casesFields); - if (isset($_FILES ['form'])) { - if (isset($_FILES["form"]["name"]) && count($_FILES["form"]["name"]) > 0) { - $oInputDocument = new InputDocument(); - $oInputDocument->uploadFileCase($_FILES, $case, $casesFields, $currentUsrUid, $appUid, $delIndex); - } - } - $wsBaseInstance = new WsBase(); - $result = $wsBaseInstance->derivateCase( - $casesFields['CURRENT_USER_UID'], $appUid, $delIndex, true - ); - $code = (is_array($result) ? $result['status_code'] : $result->status_code); - - $dataResponses = array(); - $dataResponses['ABE_REQ_UID'] = $aber; - $dataResponses['ABE_RES_CLIENT_IP'] = $_SERVER['REMOTE_ADDR']; - $dataResponses['ABE_RES_DATA'] = serialize($forms); - $dataResponses['ABE_RES_STATUS'] = 'PENDING'; - $dataResponses['ABE_RES_MESSAGE'] = ''; - - try { - require_once 'classes/model/AbeResponses.php'; - - $abeAbeResponsesInstance = new AbeResponses(); - $dataResponses['ABE_RES_UID'] = $abeAbeResponsesInstance->createOrUpdate($dataResponses); - } catch (Exception $error) { - throw $error; - } - - if ($code == 0) { - //Save Cases Notes - $dataAbeRequests = loadAbeRequest($aber); - $dataAbeConfiguration = loadAbeConfiguration($dataAbeRequests['ABE_UID']); - - if ($dataAbeConfiguration['ABE_CASE_NOTE_IN_RESPONSE'] == 1) { - $response = new stdclass(); - $response->usrUid = $casesFields['APP_DATA']['USER_LOGGED']; - $response->appUid = $appUid; - $response->delIndex = $delIndex; - $response->noteText = "Check the information that was sent for the receiver: " . $dataAbeRequests['ABE_REQ_SENT_TO']; - postNote($response); - } - - $dataAbeRequests['ABE_REQ_ANSWERED'] = 1; - $code == 0 ? uploadAbeRequest($dataAbeRequests) : ''; - - $assign = $result['message']; - $aMessage['MESSAGE'] = '' . G::loadTranslation('ID_ABE_INFORMATION_SUBMITTED') . ''; - } else { - throw new Exception('An error occurred while the application was being processed.

- Error code: ' . $result->status_code . '
- Error message: ' . $result->message . '

'); - } - - // Update - $dataResponses['ABE_RES_STATUS'] = ($code == 0 ? 'SENT' : 'ERROR'); - $dataResponses['ABE_RES_MESSAGE'] = ($code == 0 ? '-' : $result->message); - - try { - $abeAbeResponsesInstance = new AbeResponses(); - $abeAbeResponsesInstance->createOrUpdate($dataResponses); - } catch (Exception $error) { - throw $error; - } + //Now we dispatch the derivation of the case through Jobs Laravel. + $closure = function() use ($appUid, $delIndex, $aber, $dynUid, $forms, $remoteAddr, $files) { + $cases = new Cases(); + $cases->routeCaseActionByEmail($appUid, $delIndex, $aber, $dynUid, $forms, $remoteAddr, $files); + }; + JobsManager::getSingleton()->dispatch(ActionByEmail::class, $closure); + $message = []; + $message['MESSAGE'] = '' . G::loadTranslation('ID_ABE_INFORMATION_SUBMITTED') . ''; $_SESSION = unserialize($backupSession); - $G_PUBLISH->AddContent('xmlform', 'xmlform', 'login/showInfo', '', $aMessage); + $G_PUBLISH->AddContent('xmlform', 'xmlform', 'login/showInfo', '', $message); } catch (Exception $error) { - $G_PUBLISH->AddContent('xmlform', 'xmlform', 'login/showMessage', '', array('MESSAGE' => $error->getMessage() . ' Please contact to your system administrator.')); + $G_PUBLISH->AddContent('xmlform', 'xmlform', 'login/showMessage', '', ['MESSAGE' => $error->getMessage() . ' Please contact to your system administrator.']); } $_SESSION = unserialize($backupSession); G::RenderPage('publish', 'blank'); -} - +} \ No newline at end of file diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Factories/Jobs.php b/workflow/engine/src/ProcessMaker/BusinessModel/Factories/Jobs.php deleted file mode 100644 index d2df687f9..000000000 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Factories/Jobs.php +++ /dev/null @@ -1,43 +0,0 @@ -loadCase($application); @@ -195,11 +202,131 @@ trait CasesTrait } // Set users drive - End /*----------------------------------********---------------------------------*/ - + $result = [ 'appFields' => $appFields, 'triggerDebug' => $triggerDebug ]; return (object) $result; } + + /** + * This initiates the routing of the case given the application and the form + * data in the email application interface. + * @param string $appUid + * @param int $delIndex + * @param string $aber + * @param string $dynUid + * @param array $forms + * @param string $remoteAddr + * @param array $files + * @return array + * @throws Exception + */ + public function routeCaseActionByEmail($appUid, $delIndex, $aber, $dynUid, $forms, $remoteAddr, $files): array + { + //Load data related to the case + $case = new Cases(); + $fields = $case->loadCase($appUid, $delIndex); + + // Check if the current thread is not finished + if (!is_null($fields['DEL_FINISH_DATE'])) { + $message = G::loadTranslation('ID_ABE_FORM_ALREADY_FILLED'); + Log::error($message); + throw new Exception($message); + } + // Merge the data + $fields['APP_DATA'] = array_merge($fields['APP_DATA'], $forms); + + //Get current user info + $delegation = new AppDelegation(); + $currentUsrUid = $delegation->getUserAssignedInThread($appUid, $delIndex); + if (!is_null($currentUsrUid)) { + $users = new Users(); + $userInfo = $users->loadDetails($currentUsrUid); + $fields["APP_DATA"]["USER_LOGGED"] = $currentUsrUid; + $fields["APP_DATA"]["USR_USERNAME"] = $userInfo['USR_USERNAME']; + } + + foreach ($fields["APP_DATA"] as $index => $value) { + $_SESSION[$index] = $value; + } + + $fields['CURRENT_DYNAFORM'] = $dynUid; + $fields['USER_UID'] = $fields['CURRENT_USER_UID']; + + ChangeLog::getChangeLog() + ->getUsrIdByUsrUid($fields['USER_UID'], true) + ->setSourceId(ChangeLog::FromABE); + + //Update case info + $case->updateCase($appUid, $fields); + if (isset($files['form'])) { + if (isset($files["form"]["name"]) && count($files["form"]["name"]) > 0) { + $oInputDocument = new InputDocument(); + $oInputDocument->uploadFileCase($files, $case, $fields, $currentUsrUid, $appUid, $delIndex); + } + } + $wsBase = new WsBase(); + $result = $wsBase->derivateCase($fields['CURRENT_USER_UID'], $appUid, $delIndex, true); + $code = is_array($result) ? $result['status_code'] : $result->status_code; + + $dataResponses = []; + $dataResponses['ABE_REQ_UID'] = $aber; + $dataResponses['ABE_RES_CLIENT_IP'] = $remoteAddr; + $dataResponses['ABE_RES_DATA'] = serialize($forms); + $dataResponses['ABE_RES_STATUS'] = 'PENDING'; + $dataResponses['ABE_RES_MESSAGE'] = ''; + + try { + require_once 'classes/model/AbeResponses.php'; + $abeResponses = new AbeResponses(); + $dataResponses['ABE_RES_UID'] = $abeResponses->createOrUpdate($dataResponses); + } catch (Exception $error) { + $message = $error->getMessage(); + Log::error($message); + throw $error; + } + + if ($code == 0) { + //Save Cases Notes + $abeRequest = loadAbeRequest($aber); + $abeConfiguration = loadAbeConfiguration($abeRequest['ABE_UID']); + + if ($abeConfiguration['ABE_CASE_NOTE_IN_RESPONSE'] == 1) { + $response = new stdclass(); + $response->usrUid = $fields['APP_DATA']['USER_LOGGED']; + $response->appUid = $appUid; + $response->delIndex = $delIndex; + $response->noteText = "Check the information that was sent for the receiver: " . $abeRequest['ABE_REQ_SENT_TO']; + postNote($response); + } + + $abeRequest['ABE_REQ_ANSWERED'] = 1; + $code == 0 ? uploadAbeRequest($abeRequest) : ''; + } else { + $resStatusCode = is_array($result) ? $result['status_code'] : $result->status_code; + $resMessage = is_array($result) ? $result['message'] : $result->message; + $message = 'An error occurred while the application was being processed.

+ Error code: ' . $resStatusCode . '
+ Error message: ' . $resMessage . '

'; + Log::error($message); + throw new Exception($message); + } + + // Update + $resMessage = is_array($result) ? $result['message'] : $result->message; + $dataResponses['ABE_RES_STATUS'] = ($code == 0 ? 'SENT' : 'ERROR'); + $dataResponses['ABE_RES_MESSAGE'] = ($code == 0 ? '-' : $resMessage); + + try { + $abeResponses = new AbeResponses(); + $abeResponses->createOrUpdate($dataResponses); + } catch (Exception $error) { + $message = $error->getMessage(); + Log::error($message); + throw $error; + } + return $dataResponses; + } } diff --git a/workflow/engine/src/ProcessMaker/Core/JobsManager.php b/workflow/engine/src/ProcessMaker/Core/JobsManager.php index e1df5de01..cda12a92c 100644 --- a/workflow/engine/src/ProcessMaker/Core/JobsManager.php +++ b/workflow/engine/src/ProcessMaker/Core/JobsManager.php @@ -5,7 +5,6 @@ namespace ProcessMaker\Core; use Bootstrap; use Exception; use Illuminate\Support\Facades\Log; -use ProcessMaker\BusinessModel\Factories\Jobs; use ProcessMaker\Core\System; use Propel; @@ -187,7 +186,7 @@ class JobsManager { $environment = $this->getDataSnapshot(); - $instance = Jobs::create($name, function() use ($callback, $environment) { + $instance = $name::dispatch(function() use ($callback, $environment) { try { $this->recoverDataSnapshot($environment); $callback($environment); From 6c4bf31387c47e51016b6a278e4e32c2aca6b900 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Thu, 30 Apr 2020 16:08:24 -0400 Subject: [PATCH 03/24] PMCORE-1406 --- database/factories/SubApplicationFactory.php | 17 ++ .../engine/classes/DerivationTest.php | 25 ++- workflow/engine/classes/Derivation.php | 180 ++++++++++-------- .../engine/classes/model/AppDelegation.php | 5 +- .../src/ProcessMaker/Model/SubApplication.php | 51 +++++ 5 files changed, 196 insertions(+), 82 deletions(-) create mode 100644 database/factories/SubApplicationFactory.php create mode 100644 workflow/engine/src/ProcessMaker/Model/SubApplication.php diff --git a/database/factories/SubApplicationFactory.php b/database/factories/SubApplicationFactory.php new file mode 100644 index 000000000..e21488705 --- /dev/null +++ b/database/factories/SubApplicationFactory.php @@ -0,0 +1,17 @@ +define(\ProcessMaker\Model\SubApplication::class, function (Faker $faker) { + return [ + 'APP_UID' => G::generateUniqueID(), + 'APP_PARENT' => G::generateUniqueID(), + 'DEL_INDEX_PARENT' => 2, + 'DEL_THREAD_PARENT' => 1, + 'SA_STATUS' => 'ACTIVE', + 'SA_VALUES_OUT' => 'a:0:{}', + 'SA_VALUES_IN' => 'a:0:{}', + 'SA_INIT_DATE' => $faker->dateTime(), + 'SA_FINISH_DATE' => $faker->dateTime(), + ]; +}); diff --git a/tests/unit/workflow/engine/classes/DerivationTest.php b/tests/unit/workflow/engine/classes/DerivationTest.php index c2c1d8959..17fe95730 100644 --- a/tests/unit/workflow/engine/classes/DerivationTest.php +++ b/tests/unit/workflow/engine/classes/DerivationTest.php @@ -10,6 +10,7 @@ use ProcessMaker\Model\Application; use ProcessMaker\Model\Delegation; use ProcessMaker\Model\Process; use ProcessMaker\Model\Route; +use ProcessMaker\Model\SubApplication; use ProcessMaker\Model\Task; use ProcessMaker\Model\TaskUser; use ProcessMaker\Model\User; @@ -111,6 +112,8 @@ class DerivationTest extends TestCase * It tests the doDerivation method sending variables synchronously * * @covers Derivation::doDerivation() + * @covers Derivation:: + * * @test */ public function it_should_test_the_do_derivation_method_sending_variables_synchronously() @@ -151,7 +154,7 @@ class DerivationTest extends TestCase $sp = [ 'SP_VARIABLES_OUT' => 'a:1:{s:6:"@&var1";s:6:"@&var2";}', 'SP_VARIABLES_IN' => 'a:1:{s:6:"@&var2";s:6:"@&var3";}', - 'SP_SYNCHRONOUS' => '1', + 'SP_SYNCHRONOUS' => 1, 'SP_TYPE' => '', 'TAS_UID' => $task->TAS_UID, 'USR_UID' => $user->USR_UID, @@ -166,12 +169,22 @@ class DerivationTest extends TestCase // Assert the new delegation index is 1 $this->assertEquals(1, $res); + + // Review the subprocess synchronously + $query = SubApplication::query()->select(); + $query->where('APP_PARENT', $application->APP_UID); + $query->where('DEL_INDEX_PARENT', $appDelegation->DEL_INDEX); + $results = $query->get()->toArray(); + $this->assertNotEmpty($results); + $this->assertEquals($results[0]['SA_STATUS'], 'ACTIVE'); } /** * It tests the doDerivation method sending variables asynchronously * * @covers Derivation::doDerivation() + * @covers Derivation:: + * * @test */ public function it_should_test_the_do_derivation_method_sending_variables_asynchronously() @@ -225,7 +238,7 @@ class DerivationTest extends TestCase $sp = [ 'SP_VARIABLES_OUT' => 'a:1:{s:6:"@&var1";s:6:"@&var2";}', 'SP_VARIABLES_IN' => 'a:1:{s:6:"@&var2";s:6:"@&var3";}', - 'SP_SYNCHRONOUS' => '0', + 'SP_SYNCHRONOUS' => 0, 'SP_TYPE' => '', 'TAS_UID' => $task->TAS_UID, 'USR_UID' => $user->USR_UID, @@ -240,5 +253,13 @@ class DerivationTest extends TestCase // Assert the new delegation index is 1 $this->assertEquals(1, $res); + + // Review the subprocess asynchronously + $query = SubApplication::query()->select(); + $query->where('APP_PARENT', $application->APP_UID); + $query->where('DEL_INDEX_PARENT', $appDelegation->DEL_INDEX); + $results = $query->get()->toArray(); + $this->assertNotEmpty($results); + $this->assertEquals($results[0]['SA_STATUS'], 'FINISHED'); } } \ No newline at end of file diff --git a/workflow/engine/classes/Derivation.php b/workflow/engine/classes/Derivation.php index 49302cd53..b50a41ecc 100644 --- a/workflow/engine/classes/Derivation.php +++ b/workflow/engine/classes/Derivation.php @@ -5,6 +5,7 @@ */ use ProcessMaker\Model\Application as ModelApplication; +use ProcessMaker\Model\SubApplication as ModelSubApplication; class Derivation { @@ -1163,7 +1164,6 @@ class Derivation $this->verifyIsCaseChild($currentDelegation["APP_UID"], $currentDelegation["DEL_INDEX"]); } $flagUpdateCase = true; - } //The variable $iNewDelIndex will be true if we created a new index the variable @@ -1204,7 +1204,7 @@ class Derivation * @param array $appFields * @param array $aSP * - * @return integer $iNewDelIndex + * @return integer * @throws /Exception */ function doDerivation ($currentDelegation, $nextDel, $appFields, $aSP = null) @@ -1338,84 +1338,10 @@ class Derivation //if there are SubProcess to create if (isset($aSP)) { - //Check if is SelfService the task in the SubProcess - $isSelfService = (empty($aSP['USR_UID'])) ? true : false; + // Create case in the subprocess + $this->subProcessCreation($aSP, $appFields, $currentDelegation, $iNewDelIndex, $iAppThreadIndex); - //Create the new case in the sub-process - //Set the initial date to null the time its created - $aNewCase = $this->case->startCase( $aSP['TAS_UID'], $aSP['USR_UID'], true, $appFields, $isSelfService); - - //Load the TAS_UID related to the SubProcess - $taskNextDel = TaskPeer::retrieveByPK($aSP["TAS_UID"]); //Sub-Process - - //Copy case variables to sub-process case - $aFields = unserialize( $aSP['SP_VARIABLES_OUT'] ); - $aNewFields = array (); - $aOldFields = $this->case->loadCase( $aNewCase['APPLICATION'] ); - - foreach ($aFields as $sOriginField => $sTargetField) { - $sOriginField = trim($sOriginField, " @#%?$=&"); - $sTargetField = trim($sTargetField, " @#%?$=&"); - - $aNewFields[$sTargetField] = isset( $appFields['APP_DATA'][$sOriginField] ) ? $appFields['APP_DATA'][$sOriginField] : ''; - - if (array_key_exists($sOriginField . '_label', $appFields['APP_DATA'])) { - $aNewFields[$sTargetField . '_label'] = $appFields['APP_DATA'][$sOriginField . '_label']; - } - } - - //We will to update the new case - $aOldFields['APP_DATA'] = array_merge( $aOldFields['APP_DATA'], $aNewFields ); - $aOldFields['APP_STATUS'] = 'TO_DO'; - $this->case->updateCase( - $aNewCase['APPLICATION'], - $aOldFields - ); - - //Create a registry in SUB_APPLICATION table - $aSubApplication = array ( - 'APP_UID' => $aNewCase['APPLICATION'], - 'APP_PARENT' => $currentDelegation['APP_UID'], - 'DEL_INDEX_PARENT' => $iNewDelIndex, - 'DEL_THREAD_PARENT' => $iAppThreadIndex, - 'SA_STATUS' => 'ACTIVE', - 'SA_VALUES_OUT' => serialize($aNewFields), - 'SA_INIT_DATE' => date('Y-m-d H:i:s') - ); - if ($aSP['SP_SYNCHRONOUS'] == 0) { - $aSubApplication['SA_STATUS'] = 'FINISHED'; - $aSubApplication['SA_FINISH_DATE'] = $aSubApplication['SA_INIT_DATE']; - } - $oSubApplication = new SubApplication(); - $oSubApplication->create( $aSubApplication ); - - //Update the AppDelegation to execute the update trigger - $AppDelegation = AppDelegationPeer::retrieveByPK( $aNewCase['APPLICATION'], $aNewCase['INDEX'] ); - $AppDelegation->save(); - - //Create record in table APP_ASSIGN_SELF_SERVICE_VALUE - if ($taskNextDel->getTasAssignType() == "SELF_SERVICE" && trim($taskNextDel->getTasGroupVariable()) != "") { - $nextTaskGroupVariable = trim($taskNextDel->getTasGroupVariable(), " @#"); - - if (isset($aOldFields["APP_DATA"][$nextTaskGroupVariable])) { - $dataVariable = $aOldFields["APP_DATA"][$nextTaskGroupVariable]; - $dataVariable = (is_array($dataVariable))? $dataVariable : trim($dataVariable); - - if (!empty($dataVariable)) { - $appAssignSelfServiceValue = new AppAssignSelfServiceValue(); - - $appAssignSelfServiceValue->create($aNewCase["APPLICATION"], $aNewCase["INDEX"], array("PRO_UID" => $aNewCase["PROCESS"], "TAS_UID" => $aSP["TAS_UID"], "GRP_UID" => ""), $dataVariable); - } - } - } - - //We will to send the notifications - $sendNotificationsMobile = $this->sendNotificationsMobile($aOldFields, $aSP, $aNewCase['INDEX']); - $nextTaskData = $taskNextDel->toArray(BasePeer::TYPE_FIELDNAME); - $nextTaskData['USR_UID'] = $aSP['USR_UID']; - $sendNotifications = $this->notifyAssignedUser($appFields, $nextTaskData, $aNewCase['INDEX']); - - //If is ASYNCHRONOUS we will to route the case master + // If is ASYNCHRONOUS we will to route the case master if ($aSP['SP_SYNCHRONOUS'] == 0) { $this->case->setDelInitDate( $currentDelegation['APP_UID'], $iNewDelIndex ); $aDeriveTasks = $this->prepareInformation( @@ -1476,6 +1402,102 @@ class Derivation return $iNewDelIndex; } + /** + * Create the sub-process + * + * @param array $subProcessInfo + * @param array $appFields + * @param array $currentDelegation + * @param int $delIndex + * @param int $threadIndex + * + * @return void + */ + protected function subProcessCreation(array $subProcessInfo, array $appFields, array $currentDelegation, $delIndex, $threadIndex) + { + // Check if is SelfService the task in the SubProcess + $isSelfService = (empty($subProcessInfo['USR_UID'])) ? true : false; + + // Create the new case in the sub-process + // Set the initial date to null the time its created + // The DelThreadStatus will create with CLOSED value for avoid to open the case without all the execution + $newCase = $this->case->startCase($subProcessInfo['TAS_UID'], $subProcessInfo['USR_UID'], true, $appFields, $isSelfService); + + // Load the TAS_UID related to the SubProcess + $taskNextDel = TaskPeer::retrieveByPK($subProcessInfo["TAS_UID"]); //Sub-Process + + // Copy case variables to sub-process case + $fields = unserialize($subProcessInfo['SP_VARIABLES_OUT']); + $newFields = []; + $oldFields = $this->case->loadCase($newCase['APPLICATION']); + + foreach ($fields as $originField => $targetField) { + $originField = trim($originField, " @#%?$=&"); + $targetField = trim($targetField, " @#%?$=&"); + $newFields[$targetField] = isset($appFields['APP_DATA'][$originField]) ? $appFields['APP_DATA'][$originField] : ''; + + if (array_key_exists($originField . '_label', $appFields['APP_DATA'])) { + $newFields[$targetField . '_label'] = $appFields['APP_DATA'][$originField . '_label']; + } + } + + // We will to update the new case + $oldFields['APP_DATA'] = array_merge($oldFields['APP_DATA'], $newFields); + $oldFields['APP_STATUS'] = 'TO_DO'; + $this->case->updateCase( + $newCase['APPLICATION'], + $oldFields + ); + + // Create a registry in SUB_APPLICATION table + $attributes = [ + 'APP_UID' => $newCase['APPLICATION'], + 'APP_PARENT' => $currentDelegation['APP_UID'], + 'DEL_INDEX_PARENT' => $delIndex, + 'DEL_THREAD_PARENT' => $threadIndex, + 'SA_STATUS' => 'ACTIVE', + 'SA_VALUES_OUT' => serialize($newFields), + 'SA_INIT_DATE' => date('Y-m-d H:i:s') + ]; + if ($subProcessInfo['SP_SYNCHRONOUS'] == 0) { + $attributes['SA_STATUS'] = 'FINISHED'; + $attributes['SA_FINISH_DATE'] = $attributes['SA_INIT_DATE']; + } + $subprocess = ModelSubApplication::create($attributes); + + // Update the AppDelegation to execute the update trigger + // Update the DelThreadStatus, the thread is ready for continue + $appDelegation = AppDelegationPeer::retrieveByPK($newCase['APPLICATION'], $newCase['INDEX']); + $appDelegation->setDelThreadStatus('OPEN'); + $appDelegation->save(); + + // Create record in table APP_ASSIGN_SELF_SERVICE_VALUE + $tasGroupVariable = $taskNextDel->getTasGroupVariable(); + if ($taskNextDel->getTasAssignType() == "SELF_SERVICE" && !empty(trim($tasGroupVariable))) { + $nextTaskGroupVariable = trim($tasGroupVariable, " @#"); + + if (isset($oldFields["APP_DATA"][$nextTaskGroupVariable])) { + $dataVariable = $oldFields["APP_DATA"][$nextTaskGroupVariable]; + $dataVariable = (is_array($dataVariable))? $dataVariable : trim($dataVariable); + + if (!empty($dataVariable)) { + $appAssignSelfServiceValue = new AppAssignSelfServiceValue(); + $appAssignSelfServiceValue->create( + $newCase["APPLICATION"], + $newCase["INDEX"], + ["PRO_UID" => $newCase["PROCESS"], "TAS_UID" => $subProcessInfo["TAS_UID"], "GRP_UID" => ""], + $dataVariable + ); + } + } + } + // We will to send the notifications + $sendNotificationsMobile = $this->sendNotificationsMobile($oldFields, $subProcessInfo, $newCase['INDEX']); + $nextTaskData = $taskNextDel->toArray(BasePeer::TYPE_FIELDNAME); + $nextTaskData['USR_UID'] = $subProcessInfo['USR_UID']; + $sendNotifications = $this->notifyAssignedUser($appFields, $nextTaskData, $newCase['INDEX']); + } + /** * This function returns the current user Checking cases where USR_ID exists or checking a subprocess (SYNCHRONOUS) * @param $nextDel diff --git a/workflow/engine/classes/model/AppDelegation.php b/workflow/engine/classes/model/AppDelegation.php index f6c0a5d47..e0d1a39f3 100644 --- a/workflow/engine/classes/model/AppDelegation.php +++ b/workflow/engine/classes/model/AppDelegation.php @@ -181,6 +181,9 @@ class AppDelegation extends BaseAppDelegation $criteriaUpdate->add(AppDelegationPeer::DEL_LAST_INDEX, 0); BasePeer::doUpdate($criteria, $criteriaUpdate, Propel::getConnection('workflow')); + // Define the status of the thread, if is subprocess we need to CLOSED the thread + $theadStatus = !$isSubprocess ? 'OPEN' : 'CLOSED'; + $this->setAppUid($sAppUid); $this->setProUid($sProUid); $this->setTasUid($sTasUid); @@ -191,7 +194,7 @@ class AppDelegation extends BaseAppDelegation $this->setDelType('NORMAL'); $this->setDelPriority(($iPriority != '' ? $iPriority : '3')); $this->setDelThread($sAppThread); - $this->setDelThreadStatus('OPEN'); + $this->setDelThreadStatus($theadStatus); $this->setDelDelegateDate('now'); $this->setAppNumber($appNumber); $this->setTasId($taskId); diff --git a/workflow/engine/src/ProcessMaker/Model/SubApplication.php b/workflow/engine/src/ProcessMaker/Model/SubApplication.php new file mode 100644 index 000000000..615fd093e --- /dev/null +++ b/workflow/engine/src/ProcessMaker/Model/SubApplication.php @@ -0,0 +1,51 @@ + '', + 'SA_VALUES_OUT' => '', + 'SA_VALUES_IN' => '', + 'SA_INIT_DATE' => '', + 'SA_FINISH_DATE' => '' + ]; + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'APP_UID', + 'APP_PARENT', + 'DEL_INDEX_PARENT', + 'DEL_THREAD_PARENT', + 'SA_STATUS', + 'SA_VALUES_OUT', + 'SA_VALUES_IN', + 'SA_INIT_DATE', + 'SA_FINISH_DATE' + ]; +} \ No newline at end of file From 67dda5a0789373ef2ad08c2c65c05678f24bc278 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Thu, 28 May 2020 16:16:54 -0400 Subject: [PATCH 04/24] PMCORE-1387 --- database/factories/GroupwfFactory.php | 1 - database/factories/ProcessFactory.php | 3 --- database/factories/TaskFactory.php | 2 -- workflow/engine/classes/Derivation.php | 2 +- .../engine/methods/cases/cases_Derivate.php | 15 ++++++++------- .../src/ProcessMaker/Cases/CasesTrait.php | 18 +++++++++--------- .../src/ProcessMaker/Model/SubApplication.php | 1 + 7 files changed, 19 insertions(+), 23 deletions(-) diff --git a/database/factories/GroupwfFactory.php b/database/factories/GroupwfFactory.php index 0a7e77198..8dbec9610 100644 --- a/database/factories/GroupwfFactory.php +++ b/database/factories/GroupwfFactory.php @@ -7,7 +7,6 @@ use Faker\Generator as Faker; $factory->define(\ProcessMaker\Model\Groupwf::class, function(Faker $faker) { return [ 'GRP_UID' => G::generateUniqueID(), - //'GRP_ID' The incremental fields of the tables must not be specified in the creation list. 'GRP_TITLE' => $faker->sentence(2), 'GRP_STATUS' => 'ACTIVE', 'GRP_LDAP_DN' => '', diff --git a/database/factories/ProcessFactory.php b/database/factories/ProcessFactory.php index 5465309c2..4e9706ae3 100644 --- a/database/factories/ProcessFactory.php +++ b/database/factories/ProcessFactory.php @@ -9,7 +9,6 @@ $factory->define(\ProcessMaker\Model\Process::class, function(Faker $faker) { //The incremental fields of the tables must not be specified in the creation list. return [ 'PRO_UID' => G::generateUniqueID(), - //'PRO_ID' The incremental fields of the tables must not be specified in the creation list. 'PRO_TITLE' => $faker->sentence(3), 'PRO_DESCRIPTION' => $faker->paragraph(3), 'PRO_CREATE_USER' => '00000000000000000000000000000001', @@ -31,7 +30,6 @@ $factory->state(\ProcessMaker\Model\Process::class, 'foreign_keys', function (Fa $user = factory(\ProcessMaker\Model\User::class)->create(); return [ 'PRO_UID' => G::generateUniqueID(), - //'PRO_ID' The incremental fields of the tables must not be specified in the creation list. 'PRO_TITLE' => $faker->sentence(3), 'PRO_DESCRIPTION' => $faker->paragraph(3), 'PRO_CREATE_USER' => $user->USR_UID, @@ -52,7 +50,6 @@ $factory->state(\ProcessMaker\Model\Process::class, 'flow', function (Faker $fak $user = factory(\ProcessMaker\Model\User::class)->create(); $process = [ 'PRO_UID' => G::generateUniqueID(), - //'PRO_ID' The incremental fields of the tables must not be specified in the creation list. 'PRO_TITLE' => $faker->sentence(3), 'PRO_DESCRIPTION' => $faker->paragraph(3), 'PRO_CREATE_USER' => $user->USR_UID, diff --git a/database/factories/TaskFactory.php b/database/factories/TaskFactory.php index 367734efb..90bbe13a2 100644 --- a/database/factories/TaskFactory.php +++ b/database/factories/TaskFactory.php @@ -11,7 +11,6 @@ $factory->define(\ProcessMaker\Model\Task::class, function(Faker $faker) { 'PRO_UID' => $process->PRO_UID, 'PRO_ID' => $process->PRO_ID, 'TAS_UID' => G::generateUniqueID(), - //'TAS_ID' The incremental fields of the tables must not be specified in the creation list. 'TAS_TITLE' => $faker->sentence(2), 'TAS_TYPE' => 'NORMAL', 'TAS_TYPE_DAY' => 1, @@ -40,7 +39,6 @@ $factory->state(\ProcessMaker\Model\Task::class, 'foreign_keys', function (Faker 'PRO_UID' => $process->PRO_UID, 'PRO_ID' => $process->PRO_ID, 'TAS_UID' => G::generateUniqueID(), - //'TAS_ID' The incremental fields of the tables must not be specified in the creation list. 'TAS_TITLE' => $faker->sentence(2), 'TAS_TYPE' => 'NORMAL', 'TAS_TYPE_DAY' => 1, diff --git a/workflow/engine/classes/Derivation.php b/workflow/engine/classes/Derivation.php index b50a41ecc..335dedfbb 100644 --- a/workflow/engine/classes/Derivation.php +++ b/workflow/engine/classes/Derivation.php @@ -1416,7 +1416,7 @@ class Derivation protected function subProcessCreation(array $subProcessInfo, array $appFields, array $currentDelegation, $delIndex, $threadIndex) { // Check if is SelfService the task in the SubProcess - $isSelfService = (empty($subProcessInfo['USR_UID'])) ? true : false; + $isSelfService = empty($subProcessInfo['USR_UID']) ? true : false; // Create the new case in the sub-process // Set the initial date to null the time its created diff --git a/workflow/engine/methods/cases/cases_Derivate.php b/workflow/engine/methods/cases/cases_Derivate.php index 3d16da2c8..bae587c50 100644 --- a/workflow/engine/methods/cases/cases_Derivate.php +++ b/workflow/engine/methods/cases/cases_Derivate.php @@ -101,8 +101,8 @@ try { $index = $_SESSION["INDEX"]; $userLogged = $_SESSION["USER_LOGGED"]; - //Now we dispatch the derivation of the case through Jobs Laravel. - $closure = function() use($processUid, $application, $postForm, $sStatus, $flagGmail, $tasUid, $index, $userLogged) { + // Now we dispatch the derivation of the case through Jobs Laravel. + $closure = function() use ($processUid, $application, $postForm, $sStatus, $flagGmail, $tasUid, $index, $userLogged) { $cases = new Cases(); $cases->routeCase($processUid, $application, $postForm, $sStatus, $flagGmail, $tasUid, $index, $userLogged); }; @@ -114,18 +114,19 @@ try { $debuggerAvailable = true; $casesRedirector = 'casesListExtJsRedirector'; - if (isset($_SESSION ['user_experience']) && $flagGmail === false) { - $aNextStep ['PAGE'] = $casesRedirector . '?ux=' . $_SESSION ['user_experience']; + $nextStep = []; + if (isset($_SESSION['user_experience']) && $flagGmail === false) { + $nextStep['PAGE'] = $casesRedirector . '?ux=' . $_SESSION['user_experience']; $debuggerAvailable = false; } else { if ($flagGmail === true) { - $aNextStep ['PAGE'] = $casesRedirector . '?gmail=1'; + $nextStep['PAGE'] = $casesRedirector . '?gmail=1'; } else { - $aNextStep ['PAGE'] = $casesRedirector; + $nextStep['PAGE'] = $casesRedirector; } } - $loc = $aNextStep['PAGE']; + $loc = $nextStep['PAGE']; //Triggers After $isIE = Bootstrap::isIE(); diff --git a/workflow/engine/src/ProcessMaker/Cases/CasesTrait.php b/workflow/engine/src/ProcessMaker/Cases/CasesTrait.php index 8b1c65c1d..aee67bf5d 100644 --- a/workflow/engine/src/ProcessMaker/Cases/CasesTrait.php +++ b/workflow/engine/src/ProcessMaker/Cases/CasesTrait.php @@ -139,27 +139,27 @@ trait CasesTrait $this->updateCase($application, $appFields); // Send notifications - Start - $oUser = new Users(); - $aUser = $oUser->load($userLogged); - $fromName = $aUser['USR_FIRSTNAME'] . ' ' . $aUser['USR_LASTNAME']; + $user = new Users(); + $userInfo = $user->load($userLogged); + $fromName = $userInfo['USR_FIRSTNAME'] . ' ' . $userInfo['USR_LASTNAME']; - $sFromData = $fromName . ($aUser['USR_EMAIL'] != '' ? ' <' . $aUser['USR_EMAIL'] . '>' : ''); + $fromData = $fromName . ($userInfo['USR_EMAIL'] != '' ? ' <' . $userInfo['USR_EMAIL'] . '>' : ''); if ($flagGmail === true) { $appDel = new AppDelegation(); $actualThread = $appDel->Load($application, $index); $appDelPrev = $appDel->LoadParallel($application); - $Pmgmail = new Pmgmail(); + $pmGmail = new Pmgmail(); foreach ($appDelPrev as $app) { if (($app['DEL_INDEX'] != $index) && ($app['DEL_PREVIOUS'] != $actualThread['DEL_PREVIOUS'])) { - $Pmgmail->gmailsIfSelfServiceValueBased($application, $app['DEL_INDEX'], $postForm['TASKS'], $appFields['APP_DATA']); + $pmGmail->gmailsIfSelfServiceValueBased($application, $app['DEL_INDEX'], $postForm['TASKS'], $appFields['APP_DATA']); } } } try { - $this->sendNotifications($tasUid, $postForm['TASKS'], $appFields['APP_DATA'], $application, $index, $sFromData); + $this->sendNotifications($tasUid, $postForm['TASKS'], $appFields['APP_DATA'], $application, $index, $fromData); } catch (Exception $e) { G::SendTemporalMessage(G::loadTranslation('ID_NOTIFICATION_ERROR') . ' - ' . $e->getMessage(), 'warning', 'string', null, '100%'); } @@ -263,8 +263,8 @@ trait CasesTrait $case->updateCase($appUid, $fields); if (isset($files['form'])) { if (isset($files["form"]["name"]) && count($files["form"]["name"]) > 0) { - $oInputDocument = new InputDocument(); - $oInputDocument->uploadFileCase($files, $case, $fields, $currentUsrUid, $appUid, $delIndex); + $inputDocument = new InputDocument(); + $inputDocument->uploadFileCase($files, $case, $fields, $currentUsrUid, $appUid, $delIndex); } } $wsBase = new WsBase(); diff --git a/workflow/engine/src/ProcessMaker/Model/SubApplication.php b/workflow/engine/src/ProcessMaker/Model/SubApplication.php index 615fd093e..b22a03b58 100644 --- a/workflow/engine/src/ProcessMaker/Model/SubApplication.php +++ b/workflow/engine/src/ProcessMaker/Model/SubApplication.php @@ -20,6 +20,7 @@ class SubApplication extends Model protected $primaryKey = 'APP_UID'; // The IDs are auto-incrementing public $incrementing = false; + /** * The model's default values for attributes. * From ba43807f4965c5838a6dccb2870a2327f7018b80 Mon Sep 17 00:00:00 2001 From: Roly Rudy Gutierrez Pinto Date: Thu, 21 May 2020 22:19:26 -0400 Subject: [PATCH 05/24] PMCORE-1459 Identify if a datetime field has dependencies in minDate, maxDate and defaultDate property --- tests/resources/dynaform1.json | 1666 +++++++++++++++++ .../engine/classes/PmDynaformTest.php | 58 + workflow/engine/classes/PmDynaform.php | 41 + 3 files changed, 1765 insertions(+) create mode 100644 tests/resources/dynaform1.json diff --git a/tests/resources/dynaform1.json b/tests/resources/dynaform1.json new file mode 100644 index 000000000..425f7e9db --- /dev/null +++ b/tests/resources/dynaform1.json @@ -0,0 +1,1666 @@ +{ + "name": "dynaform1", + "description": "", + "items": [{ + "type": "form", + "variable": "", + "var_uid": "", + "dataType": "", + "id": "9812177395ec54cb430b028012611278", + "name": "dynaform1", + "description": "", + "mode": "edit", + "script": "", + "language": "en", + "externalLibs": "", + "printable": false, + "items": [[{ + "type": "form", + "variable": "", + "var_uid": "", + "dataType": "", + "id": "7624389535ec54b8142adc1042059709", + "name": "subdynaform1", + "description": "", + "mode": "edit", + "script": "", + "language": "en", + "externalLibs": "", + "printable": false, + "items": [[{ + "type": "text", + "variable": "textVar001", + "var_uid": "2008131825ec54bf0dfc738088201313", + "dataType": "string", + "protectedValue": false, + "id": "textVar001", + "name": "textVar001", + "label": "text_1", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "textTransform": "none", + "validate": "", + "validateMessage": "", + "maxLength": 1000, + "formula": "", + "mode": "parent", + "operation": "", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "var_name": "textVar001", + "colSpan": 3 + }, { + "type": "textarea", + "variable": "textareaVar001", + "var_uid": "3427627605ec54bf2b9ab32022122425", + "dataType": "string", + "protectedValue": false, + "id": "textareaVar001", + "name": "textareaVar001", + "label": "textarea_1", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "validate": "", + "validateMessage": "", + "mode": "parent", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "rows": "5", + "var_name": "textareaVar001", + "colSpan": 3 + }, { + "type": "dropdown", + "variable": "dropdownVar001", + "var_uid": "1689215315ec54bf4c925b5097619001", + "dataType": "string", + "protectedValue": false, + "id": "dropdownVar001", + "name": "dropdownVar001", + "label": "dropdown_1", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "var_name": "dropdownVar001", + "colSpan": 3 + }, { + "type": "checkbox", + "variable": "checkboxVar001", + "var_uid": "5306838245ec54bf77c2b81031779019", + "dataType": "boolean", + "protectedValue": false, + "id": "checkboxVar001", + "name": "checkboxVar001", + "label": "checkbox_1", + "tabIndex": "", + "defaultValue": "", + "hint": "", + "ariaLabel": "", + "ariaLabelVisible": true, + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "options": [{ + "value": "1", + "label": "true" + }, { + "value": "0", + "label": "false" + }], + "var_name": "checkboxVar001", + "colSpan": 3 + }], [{ + "type": "checkgroup", + "variable": "checkgroupVar001", + "var_uid": "6205030885ec54bf96e22e8022771313", + "dataType": "array", + "protectedValue": false, + "id": "checkgroupVar001", + "name": "checkgroupVar001", + "label": "checkgroup_1", + "tabIndex": "", + "defaultValue": "", + "hint": "", + "ariaLabel": "", + "ariaLabelVisible": true, + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "var_name": "checkgroupVar001", + "colSpan": 3 + }, { + "type": "radio", + "variable": "radioVar001", + "var_uid": "3496436525ec54bfb7fef28085429683", + "dataType": "string", + "protectedValue": false, + "id": "radioVar001", + "name": "radioVar001", + "label": "radio_1", + "tabIndex": "", + "defaultValue": "", + "hint": "", + "ariaLabel": "", + "ariaLabelVisible": true, + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "var_name": "radioVar001", + "colSpan": 3 + }, { + "type": "datetime", + "variable": "datetimeVar001", + "var_uid": "7352375875ec54bfe5ec633080735129", + "dataType": "datetime", + "protectedValue": false, + "id": "datetimeVar001", + "name": "datetimeVar001", + "label": "datetime_1", + "tabIndex": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "format": "YYYY-MM-DD", + "dayViewHeaderFormat": "MMMM YYYY", + "extraFormats": false, + "stepping": 1, + "minDate": "@@varMinDate0", + "maxDate": "@@varMaxDate0", + "useCurrent": "false", + "collapse": true, + "locale": "", + "defaultDate": "@@varDefDate0", + "disabledDates": false, + "enabledDates": false, + "icons": { + "time": "glyphicon glyphicon-time", + "date": "glyphicon glyphicon-calendar", + "up": "glyphicon glyphicon-chevron-up", + "down": "glyphicon glyphicon-chevron-down", + "previous": "glyphicon glyphicon-chevron-left", + "next": "glyphicon glyphicon-chevron-right", + "today": "glyphicon glyphicon-screenshot", + "clear": "glyphicon glyphicon-trash" + }, + "useStrict": false, + "sideBySide": false, + "daysOfWeekDisabled": false, + "calendarWeeks": false, + "viewMode": "days", + "toolbarPlacement": "default", + "showTodayButton": false, + "showClear": "false", + "widgetPositioning": { + "horizontal": "auto", + "vertical": "auto" + }, + "widgetParent": null, + "keepOpen": false, + "var_name": "datetimeVar001", + "colSpan": 3 + }, { + "type": "suggest", + "variable": "suggestVar001", + "var_uid": "2109403475ec54c01413f82044516898", + "dataType": "string", + "protectedValue": false, + "id": "suggestVar001", + "name": "suggestVar001", + "label": "suggest_1", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "delay": 0, + "resultsLimit": 10, + "forceSelection": false, + "var_name": "suggestVar001", + "colSpan": 3 + }], [{ + "type": "hidden", + "variable": "hiddenVar001", + "var_uid": "6145083135ec54c03548688097055491", + "dataType": "string", + "protectedValue": false, + "id": "hiddenVar001", + "name": "hiddenVar001", + "defaultValue": "", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "var_name": "hiddenVar001", + "colSpan": 3 + }, { + "type": "title", + "id": "title0000000001", + "label": "title_1", + "ariaLabel": "", + "colSpan": 3 + }, { + "type": "subtitle", + "id": "subtitle0000000001", + "label": "subtitle_1", + "ariaLabel": "", + "colSpan": 3 + }, { + "type": "label", + "id": "label0000000001", + "label": "label_1", + "ariaLabel": "", + "colSpan": 3 + }], [{ + "type": "link", + "id": "link0000000001", + "name": "link0000000001", + "label": "link_1", + "tabIndex": "", + "value": "", + "href": "http://www.processmaker.com/", + "hint": "", + "ariaLabel": "", + "colSpan": 3 + }, { + "type": "image", + "id": "image0000000001", + "name": "image0000000001", + "label": "image_1", + "tabIndex": "", + "hint": "", + "ariaLabel": "", + "src": "", + "shape": "", + "alternateText": "", + "comment": "", + "alt": "", + "colSpan": 3 + }, { + "type": "file", + "variable": "fileVar001", + "var_uid": "4863287415ec54c127295e7024137002", + "dataType": "file", + "protectedValue": false, + "id": "fileVar001", + "name": "fileVar001", + "label": "file_1", + "tabIndex": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "dnd": false, + "extensions": ".*", + "size": 0, + "sizeUnity": "KB", + "mode": "parent", + "multiple": false, + "inp_doc_uid": "1670605855ec549cf6f10c8028638555", + "var_name": "fileVar001", + "colSpan": 3 + }, { + "type": "multipleFile", + "variable": "multipleFileVar001", + "var_uid": "9278025435ec54c16bc80f4055495316", + "dataType": "multiplefile", + "protectedValue": false, + "id": "multipleFileVar001", + "name": "multipleFileVar001", + "label": "multipleFile_1", + "tabIndex": "", + "ariaLabel": "", + "inputDocument": "", + "required": false, + "requiredFieldErrorMessage": "", + "dnd": false, + "extensions": "*", + "size": 1024, + "sizeUnity": "KB", + "enableVersioning": false, + "mode": "parent", + "multiple": false, + "inp_doc_uid": "", + "var_name": "multipleFileVar001", + "colSpan": 3 + }], [{ + "type": "submit", + "id": "submit0000000001", + "name": "submit0000000001", + "label": "submit_1", + "tabIndex": "", + "ariaLabel": "", + "colSpan": 3 + }, { + "type": "button", + "id": "button0000000001", + "name": "button0000000001", + "label": "button_1", + "tabIndex": "", + "ariaLabel": "", + "colSpan": 3 + }, { + "type": "panel", + "id": "panel0000000001", + "content": "", + "border": "1px", + "colSpan": 3 + }, { + "colSpan": 3 + }], [{ + "type": "grid", + "variable": "gridVar001", + "var_uid": "1290123295ec54c2e9efc32010176208", + "dataType": "grid", + "protectedValue": false, + "id": "gridVar001", + "name": "gridVar001", + "label": "grid_1", + "hint": "", + "required": false, + "requiredFieldErrorMessage": "", + "columns": [{ + "type": "text", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "text0000000002", + "name": "text0000000002", + "label": "text_2", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "textTransform": "none", + "validate": "", + "validateMessage": "", + "maxLength": 1000, + "formula": "", + "mode": "parent", + "operation": "", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "columnWidth": "10", + "width": 100, + "title": "text_2", + "var_name": "gridVar001" + }, { + "type": "textarea", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "textarea0000000002", + "name": "textarea0000000002", + "label": "textarea_2", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "validate": "", + "validateMessage": "", + "mode": "parent", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "rows": "5", + "columnWidth": "10", + "width": 100, + "title": "textarea_2" + }, { + "type": "dropdown", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "dropdown0000000002", + "name": "dropdown0000000002", + "label": "dropdown_2", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "columnWidth": "10", + "width": 100, + "title": "dropdown_2" + }, { + "type": "checkbox", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "checkbox0000000002", + "name": "checkbox0000000002", + "label": "checkbox_2", + "tabIndex": "", + "defaultValue": "", + "hint": "", + "ariaLabel": "", + "ariaLabelVisible": true, + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "options": [], + "columnWidth": "10", + "width": 100, + "title": "checkbox_2" + }, { + "type": "datetime", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "datetime0000000002", + "name": "datetime0000000002", + "label": "datetime_2", + "tabIndex": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "format": "YYYY-MM-DD", + "dayViewHeaderFormat": "MMMM YYYY", + "extraFormats": false, + "stepping": 1, + "minDate": "@@varMinDate1", + "maxDate": "@@varMaxDate1", + "useCurrent": "false", + "collapse": true, + "locale": "", + "defaultDate": "@@varDefDate1", + "disabledDates": false, + "enabledDates": false, + "icons": { + "time": "glyphicon glyphicon-time", + "date": "glyphicon glyphicon-calendar", + "up": "glyphicon glyphicon-chevron-up", + "down": "glyphicon glyphicon-chevron-down", + "previous": "glyphicon glyphicon-chevron-left", + "next": "glyphicon glyphicon-chevron-right", + "today": "glyphicon glyphicon-screenshot", + "clear": "glyphicon glyphicon-trash" + }, + "useStrict": false, + "sideBySide": false, + "daysOfWeekDisabled": false, + "calendarWeeks": false, + "viewMode": "days", + "toolbarPlacement": "default", + "showTodayButton": false, + "showClear": "false", + "widgetPositioning": { + "horizontal": "auto", + "vertical": "auto" + }, + "widgetParent": null, + "keepOpen": false, + "columnWidth": "10", + "width": 100, + "title": "datetime_2" + }, { + "type": "suggest", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "suggest0000000002", + "name": "suggest0000000002", + "label": "suggest_2", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "delay": 0, + "resultsLimit": 10, + "forceSelection": false, + "columnWidth": "10", + "width": 100, + "title": "suggest_2" + }, { + "type": "hidden", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "hidden0000000002", + "name": "hidden0000000002", + "defaultValue": "", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "width": 100 + }, { + "type": "link", + "id": "link0000000002", + "name": "link0000000002", + "label": "link_2", + "tabIndex": "", + "value": "", + "href": "http://www.processmaker.com/", + "hint": "", + "ariaLabel": "", + "columnWidth": "10", + "width": 100, + "title": "link_2" + }, { + "type": "file", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "file0000000002", + "name": "file0000000002", + "label": "file_2", + "tabIndex": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "dnd": false, + "extensions": "*", + "size": 1024, + "sizeUnity": "KB", + "mode": "parent", + "multiple": false, + "inp_doc_uid": "", + "columnWidth": "10", + "width": 100, + "title": "file_2" + }, { + "type": "multipleFile", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "multipleFile0000000002", + "name": "multipleFile0000000002", + "label": "multipleFile_2", + "tabIndex": "", + "ariaLabel": "", + "inputDocument": "", + "required": false, + "requiredFieldErrorMessage": "", + "dnd": false, + "extensions": "*", + "size": 1024, + "sizeUnity": "KB", + "enableVersioning": false, + "mode": "parent", + "multiple": false, + "inp_doc_uid": "", + "columnWidth": "10", + "width": 100, + "title": "multipleFile_2" + }], + "data": [], + "mode": "parent", + "layout": "responsive", + "pageSize": "0", + "addRow": true, + "deleteRow": true, + "title": "grid_1", + "colSpan": 12 + }]], + "variables": [{ + "var_uid": "2008131825ec54bf0dfc738088201313", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "textVar001", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "3427627605ec54bf2b9ab32022122425", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "textareaVar001", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "1689215315ec54bf4c925b5097619001", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "dropdownVar001", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "5306838245ec54bf77c2b81031779019", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "checkboxVar001", + "var_field_type": "boolean", + "var_field_size": 10, + "var_label": "boolean", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[{\"value\":\"1\",\"label\":\"true\"},{\"value\":\"0\",\"label\":\"false\"}]", + "inp_doc_uid": "" + }, { + "var_uid": "6205030885ec54bf96e22e8022771313", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "checkgroupVar001", + "var_field_type": "array", + "var_field_size": 10, + "var_label": "array", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "3496436525ec54bfb7fef28085429683", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "radioVar001", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "7352375875ec54bfe5ec633080735129", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "datetimeVar001", + "var_field_type": "datetime", + "var_field_size": 10, + "var_label": "datetime", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "2109403475ec54c01413f82044516898", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "suggestVar001", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "6145083135ec54c03548688097055491", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "hiddenVar001", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "4863287415ec54c127295e7024137002", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "fileVar001", + "var_field_type": "file", + "var_field_size": 10, + "var_label": "file", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "1670605855ec549cf6f10c8028638555" + }, { + "var_uid": "9278025435ec54c16bc80f4055495316", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "multipleFileVar001", + "var_field_type": "multiplefile", + "var_field_size": 10, + "var_label": "multiplefile", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "1290123295ec54c2e9efc32010176208", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "gridVar001", + "var_field_type": "grid", + "var_field_size": 10, + "var_label": "grid", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }], + "colSpan": 12 + }], [{ + "type": "text", + "variable": "textVar002", + "var_uid": "1812914805ec54cc31a5960087359139", + "dataType": "string", + "protectedValue": false, + "id": "textVar002", + "name": "textVar002", + "label": "text_3", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "textTransform": "none", + "validate": "", + "validateMessage": "", + "maxLength": 1000, + "formula": "", + "mode": "parent", + "operation": "", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "var_name": "textVar002", + "colSpan": 3 + }, { + "type": "textarea", + "variable": "textareaVar002", + "var_uid": "4307069485ec54ce1eb8888071098926", + "dataType": "string", + "protectedValue": false, + "id": "textareaVar002", + "name": "textareaVar002", + "label": "textarea_3", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "validate": "", + "validateMessage": "", + "mode": "parent", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "rows": "5", + "var_name": "textareaVar002", + "colSpan": 3 + }, { + "type": "dropdown", + "variable": "dropdownVar002", + "var_uid": "1138214905ec54ce561e477017714089", + "dataType": "string", + "protectedValue": false, + "id": "dropdownVar002", + "name": "dropdownVar002", + "label": "dropdown_3", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "var_name": "dropdownVar002", + "colSpan": 3 + }, { + "type": "checkbox", + "variable": "checkboxVar002", + "var_uid": "9750351345ec54d21706043076575642", + "dataType": "boolean", + "protectedValue": false, + "id": "checkboxVar002", + "name": "checkboxVar002", + "label": "checkbox_3", + "tabIndex": "", + "defaultValue": "", + "hint": "", + "ariaLabel": "", + "ariaLabelVisible": true, + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "options": [{ + "value": "1", + "label": "true" + }, { + "value": "0", + "label": "false" + }], + "var_name": "checkboxVar002", + "colSpan": 3 + }], [{ + "type": "checkgroup", + "variable": "checkgroupVar002", + "var_uid": "8551499555ec54d8a3c1b52067607042", + "dataType": "array", + "protectedValue": false, + "id": "checkgroupVar002", + "name": "checkgroupVar002", + "label": "checkgroup_2", + "tabIndex": "", + "defaultValue": "", + "hint": "", + "ariaLabel": "", + "ariaLabelVisible": true, + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "var_name": "checkgroupVar002", + "colSpan": 3 + }, { + "type": "radio", + "variable": "radioVar002", + "var_uid": "9216762805ec54d8e9c15a1064391236", + "dataType": "string", + "protectedValue": false, + "id": "radioVar002", + "name": "radioVar002", + "label": "radio_2", + "tabIndex": "", + "defaultValue": "", + "hint": "", + "ariaLabel": "", + "ariaLabelVisible": true, + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "var_name": "radioVar002", + "colSpan": 3 + }, { + "type": "datetime", + "variable": "datetimeVar002", + "var_uid": "2676353185ec54d92b9d542086401008", + "dataType": "datetime", + "protectedValue": false, + "id": "datetimeVar002", + "name": "datetimeVar002", + "label": "datetime_3", + "tabIndex": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "format": "YYYY-MM-DD", + "dayViewHeaderFormat": "MMMM YYYY", + "extraFormats": false, + "stepping": 1, + "minDate": "@@varMinDate2", + "maxDate": "@@varMaxDate2", + "useCurrent": "false", + "collapse": true, + "locale": "", + "defaultDate": "@@varDefDate2", + "disabledDates": false, + "enabledDates": false, + "icons": { + "time": "glyphicon glyphicon-time", + "date": "glyphicon glyphicon-calendar", + "up": "glyphicon glyphicon-chevron-up", + "down": "glyphicon glyphicon-chevron-down", + "previous": "glyphicon glyphicon-chevron-left", + "next": "glyphicon glyphicon-chevron-right", + "today": "glyphicon glyphicon-screenshot", + "clear": "glyphicon glyphicon-trash" + }, + "useStrict": false, + "sideBySide": false, + "daysOfWeekDisabled": false, + "calendarWeeks": false, + "viewMode": "days", + "toolbarPlacement": "default", + "showTodayButton": false, + "showClear": "false", + "widgetPositioning": { + "horizontal": "auto", + "vertical": "auto" + }, + "widgetParent": null, + "keepOpen": false, + "var_name": "datetimeVar002", + "colSpan": 3 + }, { + "type": "suggest", + "variable": "suggestVar002", + "var_uid": "3315247505ec54d9d163527025569904", + "dataType": "string", + "protectedValue": false, + "id": "suggestVar002", + "name": "suggestVar002", + "label": "suggest_3", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "delay": 0, + "resultsLimit": 10, + "forceSelection": false, + "var_name": "suggestVar002", + "colSpan": 3 + }], [{ + "type": "hidden", + "variable": "hiddenVar002", + "var_uid": "4948016615ec54da5d1d8c7003880018", + "dataType": "string", + "protectedValue": false, + "id": "hiddenVar002", + "name": "hiddenVar002", + "defaultValue": "", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "var_name": "hiddenVar002", + "colSpan": 3 + }, { + "type": "title", + "id": "title0000000002", + "label": "title_2", + "ariaLabel": "", + "colSpan": 3 + }, { + "type": "subtitle", + "id": "subtitle0000000002", + "label": "subtitle_2", + "ariaLabel": "", + "colSpan": 3 + }, { + "type": "label", + "id": "label0000000002", + "label": "label_2", + "ariaLabel": "", + "colSpan": 3 + }], [{ + "type": "link", + "id": "link0000000003", + "name": "link0000000003", + "label": "link_3", + "tabIndex": "", + "value": "", + "href": "http://www.processmaker.com/", + "hint": "", + "ariaLabel": "", + "colSpan": 3 + }, { + "type": "image", + "id": "image0000000002", + "name": "image0000000002", + "label": "image_2", + "tabIndex": "", + "hint": "", + "ariaLabel": "", + "src": "", + "shape": "", + "alternateText": "", + "comment": "", + "alt": "", + "colSpan": 3 + }, { + "type": "file", + "variable": "fileVar002", + "var_uid": "9693174415ec54dbc8aac53019663138", + "dataType": "file", + "protectedValue": false, + "id": "fileVar002", + "name": "fileVar002", + "label": "file_3", + "tabIndex": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "dnd": false, + "extensions": ".*", + "size": 0, + "sizeUnity": "KB", + "mode": "parent", + "multiple": false, + "inp_doc_uid": "1670605855ec549cf6f10c8028638555", + "var_name": "fileVar002", + "colSpan": 3 + }, { + "type": "multipleFile", + "variable": "multipleFileVar002", + "var_uid": "2576759835ec54dc3339c79033907326", + "dataType": "multiplefile", + "protectedValue": false, + "id": "multipleFileVar002", + "name": "multipleFileVar002", + "label": "multipleFile_3", + "tabIndex": "", + "ariaLabel": "", + "inputDocument": "", + "required": false, + "requiredFieldErrorMessage": "", + "dnd": false, + "extensions": "*", + "size": 1024, + "sizeUnity": "KB", + "enableVersioning": false, + "mode": "parent", + "multiple": false, + "inp_doc_uid": "", + "var_name": "multipleFileVar002", + "colSpan": 3 + }], [{ + "type": "submit", + "id": "submit0000000002", + "name": "submit0000000002", + "label": "submit_2", + "tabIndex": "", + "ariaLabel": "", + "colSpan": 3 + }, { + "type": "button", + "id": "button0000000002", + "name": "button0000000002", + "label": "button_2", + "tabIndex": "", + "ariaLabel": "", + "colSpan": 3 + }, { + "type": "panel", + "id": "panel0000000002", + "content": "", + "border": "1px", + "colSpan": 3 + }, { + "colSpan": 3 + }], [{ + "type": "grid", + "variable": "gridVar002", + "var_uid": "3173579815ec54de837a8e7056159351", + "dataType": "grid", + "protectedValue": false, + "id": "gridVar002", + "name": "gridVar002", + "label": "grid_2", + "hint": "", + "required": false, + "requiredFieldErrorMessage": "", + "columns": [{ + "type": "text", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "text0000000003", + "name": "text0000000003", + "label": "text_4", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "textTransform": "none", + "validate": "", + "validateMessage": "", + "maxLength": 1000, + "formula": "", + "mode": "parent", + "operation": "", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "columnWidth": "10", + "width": 100, + "title": "text_4", + "var_name": "gridVar002" + }, { + "type": "textarea", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "textarea0000000003", + "name": "textarea0000000003", + "label": "textarea_4", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "validate": "", + "validateMessage": "", + "mode": "parent", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "rows": "5", + "columnWidth": "10", + "width": 100, + "title": "textarea_4" + }, { + "type": "dropdown", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "dropdown0000000003", + "name": "dropdown0000000003", + "label": "dropdown_4", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "columnWidth": "10", + "width": 100, + "title": "dropdown_4" + }, { + "type": "checkbox", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "checkbox0000000003", + "name": "checkbox0000000003", + "label": "checkbox_4", + "tabIndex": "", + "defaultValue": "", + "hint": "", + "ariaLabel": "", + "ariaLabelVisible": true, + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "options": [], + "columnWidth": "10", + "width": 100, + "title": "checkbox_4" + }, { + "type": "datetime", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "datetime0000000003", + "name": "datetime0000000003", + "label": "datetime_4", + "tabIndex": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "format": "YYYY-MM-DD", + "dayViewHeaderFormat": "MMMM YYYY", + "extraFormats": false, + "stepping": 1, + "minDate": "@@varMinDate3", + "maxDate": "@@varMaxDate3", + "useCurrent": "false", + "collapse": true, + "locale": "", + "defaultDate": "@@varDefDate3", + "disabledDates": false, + "enabledDates": false, + "icons": { + "time": "glyphicon glyphicon-time", + "date": "glyphicon glyphicon-calendar", + "up": "glyphicon glyphicon-chevron-up", + "down": "glyphicon glyphicon-chevron-down", + "previous": "glyphicon glyphicon-chevron-left", + "next": "glyphicon glyphicon-chevron-right", + "today": "glyphicon glyphicon-screenshot", + "clear": "glyphicon glyphicon-trash" + }, + "useStrict": false, + "sideBySide": false, + "daysOfWeekDisabled": false, + "calendarWeeks": false, + "viewMode": "days", + "toolbarPlacement": "default", + "showTodayButton": false, + "showClear": "false", + "widgetPositioning": { + "horizontal": "auto", + "vertical": "auto" + }, + "widgetParent": null, + "keepOpen": false, + "columnWidth": "10", + "width": 100, + "title": "datetime_4" + }, { + "type": "suggest", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "suggest0000000003", + "name": "suggest0000000003", + "label": "suggest_4", + "tabIndex": "", + "defaultValue": "", + "placeholder": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "mode": "parent", + "datasource": "database", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "dataVariable": "", + "options": [], + "delay": 0, + "resultsLimit": 10, + "forceSelection": false, + "columnWidth": "10", + "width": 100, + "title": "suggest_4" + }, { + "type": "hidden", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "hidden0000000003", + "name": "hidden0000000003", + "defaultValue": "", + "dbConnection": "workflow", + "dbConnectionLabel": "PM Database", + "sql": "", + "width": 100 + }, { + "type": "link", + "id": "link0000000004", + "name": "link0000000004", + "label": "link_4", + "tabIndex": "", + "value": "", + "href": "http://www.processmaker.com/", + "hint": "", + "ariaLabel": "", + "columnWidth": "10", + "width": 100, + "title": "link_4" + }, { + "type": "file", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "file0000000003", + "name": "file0000000003", + "label": "file_4", + "tabIndex": "", + "hint": "", + "ariaLabel": "", + "required": false, + "requiredFieldErrorMessage": "", + "dnd": false, + "extensions": "*", + "size": 1024, + "sizeUnity": "KB", + "mode": "parent", + "multiple": false, + "inp_doc_uid": "", + "columnWidth": "10", + "width": 100, + "title": "file_4" + }, { + "type": "multipleFile", + "variable": "", + "var_uid": "", + "dataType": "", + "protectedValue": false, + "id": "multipleFile0000000003", + "name": "multipleFile0000000003", + "label": "multipleFile_4", + "tabIndex": "", + "ariaLabel": "", + "inputDocument": "", + "required": false, + "requiredFieldErrorMessage": "", + "dnd": false, + "extensions": "*", + "size": 1024, + "sizeUnity": "KB", + "enableVersioning": false, + "mode": "parent", + "multiple": false, + "inp_doc_uid": "", + "columnWidth": "10", + "width": 100, + "title": "multipleFile_4" + }], + "data": [], + "mode": "parent", + "layout": "responsive", + "pageSize": "0", + "addRow": true, + "deleteRow": true, + "title": "grid_2", + "colSpan": 12 + }]], + "variables": [{ + "var_uid": "1812914805ec54cc31a5960087359139", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "textVar002", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "4307069485ec54ce1eb8888071098926", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "textareaVar002", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "1138214905ec54ce561e477017714089", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "dropdownVar002", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "9750351345ec54d21706043076575642", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "checkboxVar002", + "var_field_type": "boolean", + "var_field_size": 10, + "var_label": "boolean", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[{\"value\":\"1\",\"label\":\"true\"},{\"value\":\"0\",\"label\":\"false\"}]", + "inp_doc_uid": "" + }, { + "var_uid": "8551499555ec54d8a3c1b52067607042", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "checkgroupVar002", + "var_field_type": "array", + "var_field_size": 10, + "var_label": "array", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "9216762805ec54d8e9c15a1064391236", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "radioVar002", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "2676353185ec54d92b9d542086401008", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "datetimeVar002", + "var_field_type": "datetime", + "var_field_size": 10, + "var_label": "datetime", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "3315247505ec54d9d163527025569904", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "suggestVar002", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "4948016615ec54da5d1d8c7003880018", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "hiddenVar002", + "var_field_type": "string", + "var_field_size": 10, + "var_label": "string", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "9693174415ec54dbc8aac53019663138", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "fileVar002", + "var_field_type": "file", + "var_field_size": 10, + "var_label": "file", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "1670605855ec549cf6f10c8028638555" + }, { + "var_uid": "2576759835ec54dc3339c79033907326", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "multipleFileVar002", + "var_field_type": "multiplefile", + "var_field_size": 10, + "var_label": "multiplefile", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }, { + "var_uid": "3173579815ec54de837a8e7056159351", + "prj_uid": "1601276735ec545a8c3b6f2014626745", + "var_name": "gridVar002", + "var_field_type": "grid", + "var_field_size": 10, + "var_label": "grid", + "var_dbconnection": "workflow", + "var_dbconnection_label": "PM Database", + "var_sql": "", + "var_null": 0, + "var_default": "", + "var_accepted_values": "[]", + "inp_doc_uid": "" + }] + }] +} \ No newline at end of file diff --git a/tests/unit/workflow/engine/classes/PmDynaformTest.php b/tests/unit/workflow/engine/classes/PmDynaformTest.php index 5c6a10f0b..a0af45cf8 100644 --- a/tests/unit/workflow/engine/classes/PmDynaformTest.php +++ b/tests/unit/workflow/engine/classes/PmDynaformTest.php @@ -984,6 +984,64 @@ class PmDynaformTest extends TestCase // Compare the values $this->assertEquals($dynaformTitle, $dynaform->DYN_TITLE); } + + /** + * This test should verify the setDependentOptionsForDatetime() method, to + * add the dependentOptions property to the datetime control. + * @test + * @covers PmDynaform::jsonr() + * @covers PmDynaform::setDependentOptionsForDatetime() + */ + public function it_should_test_dependent_options_for_datetime_control() + { + $pathData = PATH_TRUNK . "/tests/resources/dynaform1.json"; + $data = file_get_contents($pathData); + $json = json_decode($data); + + //assert for not contain property: dependentOptions + $result = json_decode(json_encode($json), JSON_OBJECT_AS_ARRAY); + $fn = function($item) use(&$fn) { + if (is_array($item)) { + if (isset($item['type']) && $item['type'] === 'datetime') { + $this->assertArrayNotHasKey('dependentOptions', $item); + } + array_map($fn, $item); + } + }; + array_map($fn, $result); + + //assert new property: dependentOptions + $dynaform = new PmDynaform(); + $dynaform->jsonr($json); + $result = json_decode(json_encode($json), JSON_OBJECT_AS_ARRAY); + + $fn = function($item) use(&$fn) { + if (is_array($item)) { + if (isset($item['type']) && $item['type'] === 'datetime') { + $this->assertArrayHasKey('dependentOptions', $item); + $this->assertArrayHasKey('minDate', $item['dependentOptions']); + $this->assertArrayHasKey('maxDate', $item['dependentOptions']); + $this->assertArrayHasKey('defaultDate', $item['dependentOptions']); + } + array_map($fn, $item); + } + }; + array_map($fn, $result); + + $dynaform = new PmDynaform(); + $reflection = new ReflectionClass($dynaform); + $reflectionMethod = $reflection->getMethod('setDependentOptionsForDatetime'); + $reflectionMethod->setAccessible(true); + + $a = new stdClass(); + $reflectionMethod->invokeArgs($dynaform, [&$a]); + $this->assertInstanceOf('ReflectionMethod', $reflectionMethod); + + $a = new stdClass(); + $a->type = 'suggest'; + $reflectionMethod->invokeArgs($dynaform, [&$a]); + $this->assertInstanceOf('ReflectionMethod', $reflectionMethod); + } } // Dummy function used for the coverture diff --git a/workflow/engine/classes/PmDynaform.php b/workflow/engine/classes/PmDynaform.php index ab1c263fb..8ab4afdc6 100644 --- a/workflow/engine/classes/PmDynaform.php +++ b/workflow/engine/classes/PmDynaform.php @@ -29,6 +29,7 @@ class PmDynaform public $lang = SYS_LANG; public $translations = null; public $onPropertyRead = "onPropertyReadFormInstance"; + public $onAfterPropertyRead = "onAfterPropertyReadFormInstance"; public $pathRTLCss = ''; public $record = null; public $records = null; @@ -545,6 +546,7 @@ class PmDynaform if (isset($this->fields["APP_DATA"][$json->name . "_label"])) { $json->data->label = $this->fields["APP_DATA"][$json->name . "_label"]; } + $this->setDependentOptionsForDatetime($json, $this->fields); } if ($key === "type" && ($value === "file") && isset($this->fields["APP_DATA"]["APPLICATION"])) { $oCriteriaAppDocument = new Criteria("workflow"); @@ -771,6 +773,11 @@ class PmDynaform } } } + //read event after + $fn = $this->onAfterPropertyRead; + if (is_callable($fn) || function_exists($fn)) { + $fn($json, $key, $value); + } } } } @@ -2489,4 +2496,38 @@ class PmDynaform $json->dataSchema[$key] = $columnsData; } } + + /** + * Sets the dependentOptions property for datetime control, if it contains dependent fields. + * @param stdClass $json + * @param array $fields + * @return void + */ + private function setDependentOptionsForDatetime(stdClass &$json, array $fields = []): void + { + if (!isset($json->type)) { + return; + } + if ($json->type !== 'datetime') { + return; + } + $json->dependentOptions = ''; + $backup = $this->onAfterPropertyRead; + $properties = [ + 'defaultDate' => $json->defaultDate, + 'minDate' => $json->minDate, + 'maxDate' => $json->maxDate + ]; + $this->onAfterPropertyRead = function(stdClass &$json, $key, $value) use($backup, $properties) { + if (isset($json->type) && $json->type === 'datetime' && $key === "dependentOptions") { + $json->dependentOptions = new stdClass(); + foreach ($properties as $property => $value) { + if (is_string($value) && in_array(substr($value, 0, 2), self::$prefixs)) { + $json->dependentOptions->{$property} = $value; + } + } + $this->onAfterPropertyRead = $backup; + } + }; + } } From 8d9628f9055cd8541d2610645b2370b695c45c74 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Thu, 7 May 2020 16:46:03 -0400 Subject: [PATCH 06/24] PMCORE-1420 --- .../factories/ProcessVariablesFactory.php | 28 +++++++++ .../BusinessModel/VariableTest.php | 39 +++++++++++++ .../Model/ProcessVariablesTest.php | 37 ++++++++++++ .../ProcessMaker/BusinessModel/Variable.php | 38 +++++++++++++ .../ProcessMaker/Model/ProcessVariables.php | 57 ++++++++++++++++++- .../Services/Api/Project/Variable.php | 43 +++++++++++++- 6 files changed, 239 insertions(+), 3 deletions(-) diff --git a/database/factories/ProcessVariablesFactory.php b/database/factories/ProcessVariablesFactory.php index d7ee63666..77bc3ed8a 100644 --- a/database/factories/ProcessVariablesFactory.php +++ b/database/factories/ProcessVariablesFactory.php @@ -6,9 +6,37 @@ use ProcessMaker\Model\ProcessVariables; $factory->define(ProcessVariables::class, function (Faker $faker) { return [ 'VAR_UID' => G::generateUniqueID(), + 'PRO_ID' => G::generateUniqueID(), 'PRJ_UID' => G::generateUniqueID(), 'VAR_NAME' => $faker->word, 'VAR_FIELD_TYPE' => G::generateUniqueID(), + 'VAR_FIELD_TYPE_ID' => G::generateUniqueID(), + 'VAR_FIELD_SIZE' => 10, + 'VAR_LABEL' => 'string', + 'VAR_DBCONNECTION' => 'workflow', + 'VAR_SQL' => '', + 'VAR_NULL' => 0, + 'VAR_DEFAULT' => '', + 'VAR_ACCEPTED_VALUES' => '[]', + 'INP_DOC_UID' => '' + ]; +}); + +// Create a processVariables with the foreign keys +$factory->state(ProcessVariables::class, 'foreign_keys', function (Faker $faker) { + $types = ['string', 'integer', 'float', 'boolean', 'datetime', 'grid', 'array', 'file', 'multiplefile', 'object']; + $varType = $faker->randomElement($types); + $varTypeId = array_search($varType, $types) + 1; + // Create values in the foreign key relations + $process = factory(\ProcessMaker\Model\Process::class)->create(); + + return [ + 'VAR_UID' => G::generateUniqueID(), + 'PRO_ID' => $process->PRO_ID, + 'PRJ_UID' => $process->PRO_UID, + 'VAR_NAME' => $faker->word, + 'VAR_FIELD_TYPE' => $varType, + 'VAR_FIELD_TYPE_ID' => $varTypeId, 'VAR_FIELD_SIZE' => 10, 'VAR_LABEL' => 'string', 'VAR_DBCONNECTION' => 'workflow', diff --git a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/VariableTest.php b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/VariableTest.php index 794dd14f2..261e43ebd 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/VariableTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/VariableTest.php @@ -188,4 +188,43 @@ class VariableTest extends TestCase $this->assertArrayHasKey('var_accepted_values', $res, "The result does not contains 'var_accepted_values' as key"); $this->assertArrayHasKey('inp_doc_uid', $res, "The result does not contains 'inp_doc_uid' as key"); } + + /** + * Test it return the variables by type related to the PRO_UID + * + * @covers \ProcessMaker\BusinessModel\Variable::getVariablesByType() + * @test + */ + public function it_list_variables_by_type_related_a_process() + { + $process = factory(Process::class)->create(); + $varType = 'integer'; + $varTypeId = 2; + for ($x = 1; $x <= 5; $x++) { + $processVar = factory(ProcessVariables::class)->states('foreign_keys')->create([ + 'PRO_ID' => $process->PRO_ID, + 'PRJ_UID' => $process->PRO_UID, + 'VAR_FIELD_TYPE' => $varType, + 'VAR_FIELD_TYPE_ID' => $varTypeId, + 'VAR_NAME' => 'varTestName' . $x, + ]); + } + $variable = new Variable(); + // Get all results + $res = $variable->getVariablesByType($process->PRO_UID, 2); + $this->assertEquals(5, count($res)); + $res = head($res); + $this->assertArrayHasKey('value', $res, "The result does not contains 'value' as key"); + // Get a specific start and limit + $res = $variable->getVariablesByType($process->PRO_UID, 2, 0, 2); + $this->assertNotEmpty($res); + $this->assertEquals(2, count($res)); + // Get a specific search + $res = $variable->getVariablesByType($process->PRO_UID, 2, 0, 4, 'varTest'); + $this->assertNotEmpty($res); + $this->assertEquals(4, count($res)); + // When the search does not match + $res = $variable->getVariablesByType($process->PRO_UID, 2, null, null, 'other'); + $this->assertEmpty($res); + } } diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Model/ProcessVariablesTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Model/ProcessVariablesTest.php index ee2b26edc..22388239f 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Model/ProcessVariablesTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Model/ProcessVariablesTest.php @@ -70,4 +70,41 @@ class ProcessVariablesTest extends TestCase $result = ProcessVariables::getVariables($process->PRO_ID); $this->assertNotEmpty($result); } + + /** + * Test it return the variables by type related to the PRO_ID + * + * @covers \ProcessMaker\Model\ProcessVariables::getVariablesByType() + * @test + */ + public function it_list_variables_type_by_process() + { + $process = factory(Process::class)->create(); + $varType = 'integer'; + $varTypeId = 2; + for ($x = 1; $x <= 5; $x++) { + $processVar = factory(ProcessVariables::class)->states('foreign_keys')->create([ + 'PRO_ID' => $process->PRO_ID, + 'PRJ_UID' => $process->PRO_UID, + 'VAR_FIELD_TYPE' => $varType, + 'VAR_FIELD_TYPE_ID' => $varTypeId, + 'VAR_NAME' => 'varTestName' . $x, + ]); + } + + $res = ProcessVariables::getVariablesByType($processVar->PRO_ID, 2, null, null, null); + $this->assertNotEmpty($res); + $this->assertEquals(5, count($res)); + // Get a specific start and limit + $res = ProcessVariables::getVariablesByType($process->PRO_ID, 2, 0, 2); + $this->assertNotEmpty($res); + $this->assertEquals(2, count($res)); + // Get a specific search + $res = ProcessVariables::getVariablesByType($process->PRO_ID, 2, 0, 4, 'varTest'); + $this->assertNotEmpty($res); + $this->assertEquals(4, count($res)); + // When the search does not match + $res = ProcessVariables::getVariablesByType($process->PRO_ID, 2, null, null, 'other'); + $this->assertEmpty($res); + } } \ No newline at end of file diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Variable.php b/workflow/engine/src/ProcessMaker/BusinessModel/Variable.php index bcd1768b5..1d7f76b78 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Variable.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/Variable.php @@ -26,6 +26,16 @@ class Variable 'object' => 10 ]; + /** + * Get the variables types accepted + * + * @return array + */ + public function getVariableTypes() + { + return $this->variableTypes; + } + /** * Create Variable for a Process * @@ -355,6 +365,34 @@ class Variable return $arrayVariables; } + /** + * Get data of Variables related to the specific type + * + * @param string $processUid Unique id of Process + * @param int $typeVarId + * @param int $start + * @param int $limit + * @param string $search + * @param string $prefix + * + * @return array, return an array with data of a DynaForm + * @throws Exception + */ + public function getVariablesByType($processUid, $typeVarId = 0, $start = null, $limit = null, $search = null, $prefix = null) + { + //Verify data + $proId = Validator::proUid($processUid, '$prj_uid'); + $variables = ProcessVariables::getVariablesByType($proId, $typeVarId, $start, $limit, $search); + $arrayVariables = []; + foreach ($variables as $var) { + $arrayVariables[] = [ + 'value' => is_null($prefix) ? $var['VAR_NAME'] : $prefix . $var['VAR_NAME'], + ]; + } + + return $arrayVariables; + } + /** * Verify field definition * diff --git a/workflow/engine/src/ProcessMaker/Model/ProcessVariables.php b/workflow/engine/src/ProcessMaker/Model/ProcessVariables.php index 19693cab9..cdb487ddd 100644 --- a/workflow/engine/src/ProcessMaker/Model/ProcessVariables.php +++ b/workflow/engine/src/ProcessMaker/Model/ProcessVariables.php @@ -72,8 +72,21 @@ class ProcessVariables extends Model */ public function scopeProcessId($query, int $proId) { - return $query->where('PRO_ID', $proId); + return $query->where('PROCESS_VARIABLES.PRO_ID', $proId); } + + /** + * Scope a query to filter an specific type for variable + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param int $typeId + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeTypeId($query, int $typeId) + { + return $query->where('VAR_FIELD_TYPE_ID', $typeId); + } + /** * Return the variables list * @@ -96,4 +109,46 @@ class ProcessVariables extends Model return $variablesList; } + + /** + * Return the variables list + * + * @param int $proId + * @param int $typeId + * @param int $start + * @param int $limit + * @param string $search + * + * @return array + */ + public static function getVariablesByType(int $proId, int $typeId = 0, $start = null, $limit = null, $search = null) + { + $query = ProcessVariables::query()->select(); + $query->leftJoin('DB_SOURCE', function ($join) { + $join->on('DB_SOURCE.PRO_ID', '=', 'PROCESS_VARIABLES.PRO_ID'); + }); + $query->processId($proId); + // Check if we need to filter the type of variables + if ($typeId > 0) { + $query->typeId($typeId); + } + // search a specific variable name + if (!empty($search)) { + $query->where('VAR_NAME', 'LIKE', "${search}%"); + } + // orde by varNane + $query->orderBy('VAR_NAME', 'ASC'); + // Check if we need to add a pagination + if(!is_null($start) && !is_null($limit)) { + $query->offset($start)->limit($limit); + } + // Get records + $results = $query->get(); + $variablesList = []; + $results->each(function ($item, $key) use (&$variablesList) { + $variablesList[] = $item->toArray(); + }); + + return $variablesList; + } } \ No newline at end of file diff --git a/workflow/engine/src/ProcessMaker/Services/Api/Project/Variable.php b/workflow/engine/src/ProcessMaker/Services/Api/Project/Variable.php index 13e65d343..46bbd585b 100644 --- a/workflow/engine/src/ProcessMaker/Services/Api/Project/Variable.php +++ b/workflow/engine/src/ProcessMaker/Services/Api/Project/Variable.php @@ -1,8 +1,12 @@ getVariableTypes())])); + } + // Review if the word has the prefix + $count = preg_match_all('/\@(?:([\@\%\#\?\$\=\&Qq\!])|([a-zA-Z\_][\w\-\>\:]*)\(((?:[^\\\\\)]*(?:[\\\\][\w\W])?)*)\))((?:\s*\[[\'"]?\w+[\'"]?\])+|\-\>([a-zA-Z\_]\w*))?/', $search, $match, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE); + // Check if the search has some prefix + $prefix = ''; + if ($count) { + $prefix = substr($search,0,2); + $search = substr($search,2); + } + $response = $variable->getVariablesByType($prj_uid, $typeVatId, $start, $limit, $search, $prefix); + + return $response; + } catch (Exception $e) { + throw (new RestException(Api::STAT_APP_EXCEPTION, $e->getMessage())); + } + } + /** * @url GET /:prj_uid/process-variable/:var_uid * From e0006797d32965d80dd15d91d849b79f4d88939a Mon Sep 17 00:00:00 2001 From: fabio Date: Tue, 26 May 2020 21:50:07 -0400 Subject: [PATCH 07/24] PMCORE-506: Processmaker dynaform designer should allow same ID or Name between grids Grids --- workflow/engine/src/ProcessMaker/BusinessModel/DynaForm.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/DynaForm.php b/workflow/engine/src/ProcessMaker/BusinessModel/DynaForm.php index 2c5f74e19..1b38e2b33 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/DynaForm.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/DynaForm.php @@ -1178,7 +1178,9 @@ class DynaForm } foreach ($oldColumns as $oldColumn) { if (strtolower(AdditionalTables::getPHPName($column->id)) === strtolower(AdditionalTables::getPHPName($oldColumn->id))) { - $identicals[] = "'" . $column->id . "' - '" . $oldColumn->id . "'"; + if (strtolower(AdditionalTables::getPHPName($column->var_name)) === strtolower(AdditionalTables::getPHPName($oldColumn->var_name))) { + $identicals[] = "'" . $column->id . "' - '" . $oldColumn->id . "'"; + } } } } From 1e508a24509e942bd9d2054adcb54ae4211fe28b Mon Sep 17 00:00:00 2001 From: Fabio Guachalla Date: Wed, 3 Jun 2020 12:45:34 -0400 Subject: [PATCH 08/24] PMCORE-508:Create the pmfunction PMFTotalCalculation( "GRID_ID" , "COLUMN" , "FUNCTION" ) to get the total calculation of columns Correction unit test Coverage Median Par Minimun Test --- gulliver/js/codemirror/addon/hint/php-hint.js | 6 +- .../PmFunctions/PMFTotalCalculationTest.php | 260 ++++++++++++++++++ workflow/engine/classes/class.pmFunctions.php | 117 ++++++++ .../engine/src/ProcessMaker/Core/System.php | 3 +- 4 files changed, 384 insertions(+), 2 deletions(-) create mode 100644 tests/unit/workflow/engine/classes/PmFunctions/PMFTotalCalculationTest.php diff --git a/gulliver/js/codemirror/addon/hint/php-hint.js b/gulliver/js/codemirror/addon/hint/php-hint.js index 320dfe2a8..2f535dcd4 100644 --- a/gulliver/js/codemirror/addon/hint/php-hint.js +++ b/gulliver/js/codemirror/addon/hint/php-hint.js @@ -90,6 +90,10 @@ var evaluateFunctionFunction = [evaluateFunction+"($gridName,$Expression)"]; arrayFunctions[evaluateFunction] = evaluateFunctionFunction; + var PMFTotalCalculation = "PMFTotalCalculation"; + var PMFTotalCalculationFunction = [PMFTotalCalculation + "($gridName, $field, 'function')"]; + arrayFunctions[PMFTotalCalculation] = PMFTotalCalculationFunction; + var PMFTaskCase = "PMFTaskCase"; var PMFTaskCaseFunction = [PMFTaskCase+"($caseId)"]; arrayFunctions[PMFTaskCase] = PMFTaskCaseFunction; @@ -219,7 +223,7 @@ arrayFunctions[PMFGetCaseNotes] = PMFGetCaseNotesFunction; var phpPMFunctions = [formatDate,getCurrentDate,getCurrentTime,literalDate,capitalize,lowerCase,upperCase,userInfo,executeQuery,orderGrid, - evaluateFunction,PMFTaskCase,PMFTaskList,PMFUserList,PMFGroupList,PMFRoleList,PMFCaseList,PMFProcessList,PMFSendVariables,PMFDerivateCase, + evaluateFunction, PMFTotalCalculation, PMFTaskCase,PMFTaskList,PMFUserList,PMFGroupList,PMFRoleList,PMFCaseList,PMFProcessList,PMFSendVariables,PMFDerivateCase, PMFNewCaseImpersonate,PMFNewCase,PMFPauseCase,PMFUnpauseCase,PMFAssignUserToGroup,PMFCreateUser,PMFUpdateUser,PMFInformationUser, generateCode,setCaseTrackerCode,jumping,PMFRedirectToStep,pauseCase,PMFSendMessage,PMFgetLabelOption,PMFGenerateOutputDocument, PMFGetUserEmailAddress,PMFGetNextAssignedUser,PMFDeleteCase,PMFCancelCase,PMFAddInputDocument,PMFAddCaseNote,PMFGetCaseNotes]; diff --git a/tests/unit/workflow/engine/classes/PmFunctions/PMFTotalCalculationTest.php b/tests/unit/workflow/engine/classes/PmFunctions/PMFTotalCalculationTest.php new file mode 100644 index 000000000..1624a21b2 --- /dev/null +++ b/tests/unit/workflow/engine/classes/PmFunctions/PMFTotalCalculationTest.php @@ -0,0 +1,260 @@ + [ + "field1" => "Value 1", + "field2" => 2 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 5 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 3 + ] + ]; + $field = "field2"; + $this->assertEquals(10, PMFTotalCalculation($grid, $field, 'sum')); + } + /** + * This tests if the "PMFTotalCalculation" execute correctly the average + * @test + */ + public function it_must_return_the_average_of_the_method() + { + $grid = [ + '1' => [ + "field1" => "Value 1", + "field2" => 2 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 5 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 3 + ] + ]; + $this->assertEquals(3.3333333333, PMFTotalCalculation($grid, 'field2', 'average')); + } + /** + * This tests if the "PMFTotalCalculation" execute correctly the median + * @test + */ + public function it_must_return_the_median_of_the_method() + { + $grid1 = [ + '1' => [ + "field1" => "Value 1", + "field2" => 2 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 5 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 3 + ] + ]; + $grid2 = [ + '1' => [ + "field1" => "Value 1", + "field2" => 2 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 5 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 3 + ], + '4' => [ + "field1" => "Value 3", + "field2" => 8 + ] + ]; + $this->assertEquals(3, PMFTotalCalculation($grid1, 'field2', 'median')); + $this->assertEquals(4, PMFTotalCalculation($grid2, 'field2', 'median')); + } + /** + * This tests if the "PMFTotalCalculation" execute correctly the minimum + * @test + */ + public function it_must_return_the_minimum_of_the_method() + { + $grid = [ + '1' => [ + "field1" => "Value 1", + "field2" => 5 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 2 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 3 + ] + ]; + $this->assertEquals(2, PMFTotalCalculation($grid, 'field2', 'minimum')); + } + /** + * This tests if the "PMFTotalCalculation" execute correctly the maximum + * @test + */ + public function it_must_return_the_maximum_of_the_method() + { + $grid = [ + '1' => [ + "field1" => "Value 1", + "field2" => 2 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 5 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 3 + ] + ]; + $this->assertEquals(5, PMFTotalCalculation($grid, 'field2', 'maximum')); + } + /** + * This tests if the "PMFTotalCalculation" execute correctly the standardDeviation + * @test + */ + public function it_must_return_the_standardDeviation_of_the_method() + { + $grid = [ + '1' => [ + "field1" => "Value 1", + "field2" => 25 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 40 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 10 + ] + ]; + $this->assertEquals(12.2474487139, PMFTotalCalculation($grid, 'field2', 'standardDeviation')); + } + /** + * This tests if the "PMFTotalCalculation" execute correctly the variance + * @test + */ + public function it_must_return_the_variance_of_the_method() + { + $grid = [ + '1' => [ + "field1" => "Value 1", + "field2" => 25 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 40 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 10 + ] + ]; + $this->assertEquals(150, PMFTotalCalculation($grid, 'field2', 'variance')); + } + /** + * This tests if the "PMFTotalCalculation" execute correctly the percentile + * @test + */ + public function it_must_return_the_percentile_of_the_method() + { + $grid = [ + '1' => [ + "field1" => "Value 1", + "field2" => 10 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 35 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 5 + ] + ]; + $expectedArray = [ + "0" => 20, + "1" => 70, + "2" => 10, + ]; + $this->assertEquals($expectedArray, PMFTotalCalculation($grid, 'field2', 'percentile')); + } + /** + * This tests if the "PMFTotalCalculation" execute correctly the count + * @test + */ + public function it_must_return_the_count_of_the_method() + { + $grid = [ + '1' => [ + "field1" => "Value 1", + "field2" => 25 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 40 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 10 + ] + ]; + $this->assertEquals(3, PMFTotalCalculation($grid, 'field2', 'count')); + } + /** + * This tests if the "PMFTotalCalculation" execute correctly the count distinct + * @test + */ + public function it_must_return_the_count_distinct_of_the_method() + { + $grid = [ + '1' => [ + "field1" => "Value 1", + "field2" => 20 + ], + '2' => [ + "field1" => "Value 2", + "field2" => 20 + ], + '3' => [ + "field1" => "Value 3", + "field2" => 10 + ] + ]; + $this->assertEquals(2, PMFTotalCalculation($grid, 'field2', 'countDistinct')); + } +} \ No newline at end of file diff --git a/workflow/engine/classes/class.pmFunctions.php b/workflow/engine/classes/class.pmFunctions.php index e99854804..37d40f81b 100644 --- a/workflow/engine/classes/class.pmFunctions.php +++ b/workflow/engine/classes/class.pmFunctions.php @@ -446,6 +446,123 @@ function evaluateFunction($aGrid, $sExpresion) return $aGrid; } +/** + * + * @method + * + * Executes operations in the grid fields, such as sum, average, median, minimum, maximun, + * stantard derivation, variance, percentile, count, count distinct + * + * @name PMFTotalCalculation + * @label PMFTotalCalculation Function + * @link http://wiki.processmaker.com/index.php/ProcessMaker_Functions#PMFTotalCalculation.28.29 + * @param array | $grid | Grid | The input grid. + * @param string (32) | $field | Name of field | The name of the field. + * @param string (32) | $function | Operation. + * @return object|array | $result | Result | Result according of the function + * + */ +function PMFTotalCalculation($grid, $field, $function) +{ + $systemConfiguration = Bootstrap::getSystemConfiguration(); + $floatPointNumber = $systemConfiguration['PMFTOTALCALCULATION_FLOATING_POINT_NUMBER']; + $function = strtolower($function); + $totalRows = count($grid); + $result = 0; + $sum = 0; + + switch ($function) { + case "sum": + for ($i = 1; $i <= $totalRows; $i += 1) { + $result += $grid[$i][$field]; + } + break; + case "average": + for ($i = 1; $i <= $totalRows; $i += 1) { + $result += $grid[$i][$field]; + } + $result = $result / $totalRows; + break; + case "median": + $arrayAux = []; + for ($i = 1; $i <= $totalRows; $i += 1) { + $arrayAux[] = $grid[$i][$field]; + } + sort($arrayAux); + $term = ($totalRows + 1) / 2; + if ($totalRows % 2 === 0) { + $term = floor($term); + $result = ($arrayAux[$term - 1] + $arrayAux[$term]) / 2; + } else { + $result = $arrayAux[$term - 1]; + } + break; + case "minimum": + $result = $grid[1][$field]; + for ($i = 2; $i <= $totalRows; $i += 1) { + if ($grid[$i][$field] < $result) { + $result = $grid[$i][$field]; + } + } + break; + case "maximum": + $result = $grid[1][$field]; + for ($i = 2; $i <= $totalRows; $i += 1) { + if ($grid[$i][$field] > $result) { + $result = $grid[$i][$field]; + } + } + break; + case "standarddeviation": + $mean = 0; + for ($i = 1; $i <= $totalRows; $i += 1) { + $mean += $grid[$i][$field]; + } + $mean = $mean / $totalRows; + for ($i = 1; $i <= $totalRows; $i += 1) { + $result += pow($grid[$i][$field] - $mean, 2); + } + $result = sqrt($result / $totalRows); + break; + case "variance": + $mean = 0; + for ($i = 1; $i <= $totalRows; $i += 1) { + $mean += $grid[$i][$field]; + } + $mean = $mean / $totalRows; + for ($i = 1; $i <= $totalRows; $i += 1) { + $result += pow($grid[$i][$field] - $mean, 2); + } + $result = $result / $totalRows; + break; + case "percentile": + $result = []; + $arrayAux = []; + for ($i = 1; $i <= $totalRows; $i += 1) { + $sum += $grid[$i][$field]; + $arrayAux[] = $grid[$i][$field]; + } + for ($i = 0; $i < count($arrayAux); $i += 1) { + $result[] = round(($arrayAux[$i] * 100) / $sum, $floatPointNumber); + } + break; + case "count": + $result = $totalRows; + break; + case "countdistinct": + $arrayAux = []; + for ($i = 1; $i <= $totalRows; $i += 1) { + $arrayAux[] = $grid[$i][$field]; + } + $result = count(array_count_values($arrayAux)); + break; + } + if ($function !== "percentile") { + return round($result, $floatPointNumber); + } + return $result; +} + /** * Web Services Functions * */ diff --git a/workflow/engine/src/ProcessMaker/Core/System.php b/workflow/engine/src/ProcessMaker/Core/System.php index 91ad8ab49..9b35594e5 100644 --- a/workflow/engine/src/ProcessMaker/Core/System.php +++ b/workflow/engine/src/ProcessMaker/Core/System.php @@ -78,7 +78,8 @@ class System 'highlight_home_folder_enable' => 0, 'highlight_home_folder_refresh_time' => 10, 'highlight_home_folder_scope' => 'unassigned', // For now only this list is supported - 'disable_advanced_search_case_title_fulltext' => 0 + 'disable_advanced_search_case_title_fulltext' => 0, + 'PMFTOTALCALCULATION_FLOATING_POINT_NUMBER' => 10 ]; /** From 557f3634e57797923fa218844074db38110d590b Mon Sep 17 00:00:00 2001 From: Fabio Guachalla Date: Mon, 8 Jun 2020 17:25:28 -0400 Subject: [PATCH 09/24] CR CR --- .../classes/PmFunctions/PMFTotalCalculationTest.php | 6 +++--- workflow/engine/classes/class.pmFunctions.php | 10 +++++----- .../engine/src/ProcessMaker/BusinessModel/Variable.php | 3 +-- .../engine/src/ProcessMaker/Model/ProcessVariables.php | 4 ++-- .../src/ProcessMaker/Services/Api/Project/Variable.php | 4 +++- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/unit/workflow/engine/classes/PmFunctions/PMFTotalCalculationTest.php b/tests/unit/workflow/engine/classes/PmFunctions/PMFTotalCalculationTest.php index 1624a21b2..3b28b59ff 100644 --- a/tests/unit/workflow/engine/classes/PmFunctions/PMFTotalCalculationTest.php +++ b/tests/unit/workflow/engine/classes/PmFunctions/PMFTotalCalculationTest.php @@ -207,9 +207,9 @@ class PMFTotalCalculationTest extends TestCase ] ]; $expectedArray = [ - "0" => 20, - "1" => 70, - "2" => 10, + "1" => 20, + "2" => 70, + "3" => 10, ]; $this->assertEquals($expectedArray, PMFTotalCalculation($grid, 'field2', 'percentile')); } diff --git a/workflow/engine/classes/class.pmFunctions.php b/workflow/engine/classes/class.pmFunctions.php index 37d40f81b..262fa78f1 100644 --- a/workflow/engine/classes/class.pmFunctions.php +++ b/workflow/engine/classes/class.pmFunctions.php @@ -451,7 +451,7 @@ function evaluateFunction($aGrid, $sExpresion) * @method * * Executes operations in the grid fields, such as sum, average, median, minimum, maximun, - * stantard derivation, variance, percentile, count, count distinct + * stantard deviation, variance, percentile, count, count distinct * * @name PMFTotalCalculation * @label PMFTotalCalculation Function @@ -459,7 +459,7 @@ function evaluateFunction($aGrid, $sExpresion) * @param array | $grid | Grid | The input grid. * @param string (32) | $field | Name of field | The name of the field. * @param string (32) | $function | Operation. - * @return object|array | $result | Result | Result according of the function + * @return int|float|array | $result | Result | Result according of the function * */ function PMFTotalCalculation($grid, $field, $function) @@ -540,10 +540,10 @@ function PMFTotalCalculation($grid, $field, $function) $arrayAux = []; for ($i = 1; $i <= $totalRows; $i += 1) { $sum += $grid[$i][$field]; - $arrayAux[] = $grid[$i][$field]; + $arrayAux[$i] = $grid[$i][$field]; } - for ($i = 0; $i < count($arrayAux); $i += 1) { - $result[] = round(($arrayAux[$i] * 100) / $sum, $floatPointNumber); + for ($i = 1; $i <= count($arrayAux); $i += 1) { + $result[$i] = round(($arrayAux[$i] * 100) / $sum, $floatPointNumber); } break; case "count": diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Variable.php b/workflow/engine/src/ProcessMaker/BusinessModel/Variable.php index 1d7f76b78..f51538af3 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Variable.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/Variable.php @@ -375,8 +375,7 @@ class Variable * @param string $search * @param string $prefix * - * @return array, return an array with data of a DynaForm - * @throws Exception + * @return array, return an array with varaibles filter by type */ public function getVariablesByType($processUid, $typeVarId = 0, $start = null, $limit = null, $search = null, $prefix = null) { diff --git a/workflow/engine/src/ProcessMaker/Model/ProcessVariables.php b/workflow/engine/src/ProcessMaker/Model/ProcessVariables.php index cdb487ddd..8647380e7 100644 --- a/workflow/engine/src/ProcessMaker/Model/ProcessVariables.php +++ b/workflow/engine/src/ProcessMaker/Model/ProcessVariables.php @@ -76,7 +76,7 @@ class ProcessVariables extends Model } /** - * Scope a query to filter an specific type for variable + * Scope a query to filter a specific type for variable * * @param \Illuminate\Database\Eloquent\Builder $query * @param int $typeId @@ -136,7 +136,7 @@ class ProcessVariables extends Model if (!empty($search)) { $query->where('VAR_NAME', 'LIKE', "${search}%"); } - // orde by varNane + // order by varNane $query->orderBy('VAR_NAME', 'ASC'); // Check if we need to add a pagination if(!is_null($start) && !is_null($limit)) { diff --git a/workflow/engine/src/ProcessMaker/Services/Api/Project/Variable.php b/workflow/engine/src/ProcessMaker/Services/Api/Project/Variable.php index 46bbd585b..af3772de1 100644 --- a/workflow/engine/src/ProcessMaker/Services/Api/Project/Variable.php +++ b/workflow/engine/src/ProcessMaker/Services/Api/Project/Variable.php @@ -3,9 +3,9 @@ namespace ProcessMaker\Services\Api\Project; use Exception; use G; +use Luracast\Restler\RestException; use ProcessMaker\BusinessModel\Variable as BmVariable; use ProcessMaker\Services\Api; -use Luracast\Restler\RestException; /** * Project\Variable Api Controller @@ -33,6 +33,8 @@ class Variable extends Api } /** + * Get variables by type + * * @url GET /:prj_uid/process-variables/:typeVariable/paged * * @param string $prj_uid {@min 32}{@max 32} From 2757bf7f644e205e82772e62b7709c8c76ecacc6 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Thu, 4 Jun 2020 10:38:48 -0400 Subject: [PATCH 10/24] PMCORE-1542 --- database/factories/DocumentsFactory.php | 54 +++++ gulliver/system/class.g.php | 2 + tests/resources/images/activate.png | Bin 0 -> 750 bytes tests/resources/images/activity.gif | Bin 0 -> 6610 bytes .../ProcessMaker/BusinessModel/CasesTest.php | 59 ++++++ .../src/ProcessMaker/Model/DocumentsTest.php | 29 +++ .../Util/Helpers/SaveAppDocumentTest.php | 47 +++++ workflow/engine/classes/WsBase.php | 21 +- workflow/engine/classes/class.pmFunctions.php | 6 +- .../model/map/AppDocumentMapBuilder.php | 4 +- .../classes/model/map/AppNotesMapBuilder.php | 4 +- .../classes/model/om/BaseAppDocument.php | 186 ++++++++++++------ .../classes/model/om/BaseAppDocumentPeer.php | 23 ++- .../engine/classes/model/om/BaseAppNotes.php | 156 ++++++++++----- .../classes/model/om/BaseAppNotesPeer.php | 23 ++- workflow/engine/config/schema.xml | 9 +- workflow/engine/data/mysql/schema.sql | 3 + .../src/ProcessMaker/BusinessModel/Cases.php | 134 +++++++++++++ .../src/ProcessMaker/Model/AppNotes.php | 36 ++++ .../src/ProcessMaker/Model/Documents.php | 96 +++++++++ .../engine/src/ProcessMaker/Util/helpers.php | 36 ++++ 21 files changed, 781 insertions(+), 147 deletions(-) create mode 100644 database/factories/DocumentsFactory.php create mode 100644 tests/resources/images/activate.png create mode 100644 tests/resources/images/activity.gif create mode 100644 tests/unit/workflow/engine/src/ProcessMaker/Model/DocumentsTest.php create mode 100644 tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/SaveAppDocumentTest.php create mode 100644 workflow/engine/src/ProcessMaker/Model/Documents.php diff --git a/database/factories/DocumentsFactory.php b/database/factories/DocumentsFactory.php new file mode 100644 index 000000000..acfafb0ce --- /dev/null +++ b/database/factories/DocumentsFactory.php @@ -0,0 +1,54 @@ +define(\ProcessMaker\Model\Documents::class, function (Faker $faker) { + $types = ['INPUT', 'OUTPUT', 'ATTACHED']; + $type = $faker->randomElement($types); + return [ + 'APP_DOC_UID' => G::generateUniqueID(), + 'APP_DOC_FILENAME' => 'image.png', + 'APP_DOC_TITLE' => '', + 'APP_DOC_COMMENT' => '', + 'DOC_VERSION' => 1, + 'APP_UID' => G::generateUniqueID(), + 'DEL_INDEX' => 1, + 'DOC_UID' => G::generateUniqueID(), + 'USR_UID' => G::generateUniqueID(), + 'APP_DOC_TYPE' => $type, + 'APP_DOC_CREATE_DATE' => $faker->date(), + 'APP_DOC_INDEX' => 1, + 'FOLDER_UID' => G::generateUniqueID(), + 'APP_DOC_PLUGIN' => '', + 'APP_DOC_TAGS' => '', + 'APP_DOC_STATUS' => 'ACTIVE', + 'APP_DOC_STATUS_DATE' => $faker->date(), + 'APP_DOC_FIELDNAME' => '', + 'APP_DOC_DRIVE_DOWNLOAD' => '', + ]; +}); + +// Create a dynaform with the foreign keys +$factory->state(\ProcessMaker\Model\Documents::class, 'case_notes', function (Faker $faker) { + return [ + 'APP_DOC_UID' => G::generateUniqueID(), + 'APP_DOC_FILENAME' => 'image.png', + 'APP_DOC_TITLE' => '', + 'APP_DOC_COMMENT' => '', + 'DOC_VERSION' => 1, + 'APP_UID' => G::generateUniqueID(), + 'DEL_INDEX' => 1, + 'DOC_UID' => G::generateUniqueID(), + 'USR_UID' => G::generateUniqueID(), + 'APP_DOC_TYPE' => 'CASE_NOTE', + 'APP_DOC_CREATE_DATE' => $faker->date(), + 'APP_DOC_INDEX' => 1, + 'FOLDER_UID' => G::generateUniqueID(), + 'APP_DOC_PLUGIN' => '', + 'APP_DOC_TAGS' => '', + 'APP_DOC_STATUS' => 'ACTIVE', + 'APP_DOC_STATUS_DATE' => $faker->date(), + 'APP_DOC_FIELDNAME' => '', + 'APP_DOC_DRIVE_DOWNLOAD' => '', + ]; +}); diff --git a/gulliver/system/class.g.php b/gulliver/system/class.g.php index ee129521e..8e9e9af86 100644 --- a/gulliver/system/class.g.php +++ b/gulliver/system/class.g.php @@ -2708,6 +2708,8 @@ class G $nameToSave = $filter->validateInput($nameToSave, "path"); @chmod($path . "/" . $nameToSave, $permission); umask($oldumask); + + return true; } catch (Exception $oException) { throw $oException; } diff --git a/tests/resources/images/activate.png b/tests/resources/images/activate.png new file mode 100644 index 0000000000000000000000000000000000000000..1be605c56c14b9b49d46d78d8f269193f1908316 GIT binary patch literal 750 zcmVPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igi2 z6&wl0^#N%B00M4FL_t(I%cYXvOOsI?#-Gj2HJN7FQZ^jRJ2ituvx1N=Gy6BXjVQd4 zj3N?@b`kyl2fFE|4C$g9wJfx&rCS4o+RVW=b#uP&dwzaT7tR;Wx`-Y)HwT{2d7kh2 zdW50WIv5R5Tk#CkrjExgQB)7Jnbc08V)3S-I93z1;-aqr>Yc!3pAZFJ1z@gQq|ha?3>3W5YUF*k)E0j@dHn<+#ZBG5+1TmUD=Y-Kmv%7(0U z-ffh-08Dc*?LaccT!5`?!-=6PtB^+SGYXOrdOMM13XWn>wlXe2kO)dr*pM3lu;)ct zt0ScZ34*dD3<$(^5CFUYP?43Ktu2HDVU$G~RawQBv|uS47PlZA4rNJ*vOrVoK`_lB zHe}8P6s_XOY++{gSg2zg;#6R93*6c^$}I)HF3=clM1M;^W`9htvH}~~vqiMy2&Y@mU?wyDZEa!w>e9XRge%okz gol0cHUjhHBUvhNKI-;$=ZvX%Q07*qoM6N<$g00L$VgLXD literal 0 HcmV?d00001 diff --git a/tests/resources/images/activity.gif b/tests/resources/images/activity.gif new file mode 100644 index 0000000000000000000000000000000000000000..94d46c129f6bba02f2f0b230d9f4d2c7a48e1412 GIT binary patch literal 6610 zcmbW5S2!H(x`u}_hB11{5DYU2LV}PeAqax#(TQlmNR(*NjUK)C-Y239QKR?Xd+)tO z|A{SYueJBS);ik%*?agM+}Hhl&+|$~ieJEB7C;VY1pqobI~y7rva_>GN=ga~3x|e= zrl+Up=H}Md*GEQ1nwy*3+uN(Ft1Buh8XFt?`}>cMj&^r<4-O9M>gsxXdlM28#>dAo z7|hbrQfq7L*49>PYHDFMe1?JeUifr6y!o0norl01*NfLH(kfbkLsga?2F zIPPx1pJxC-w~G%#jZvv)`9&Xhep+)3*jX$%79F@A+%~3LOJB?3#AMraeH2U^#MsFe zY8OuyIIzcmX9>YFXk_&U#g#hG5>_pAoFw)sO&$ z+up;;HY@_m(+e1k4owL6jk41>A=G>L))+uR3<6~qkz-}!ms3?x5gA)smQ_|ls%z@% ze>7S**EiJELR%`jx;uWh)%3M?m-hAzG)=Y4w9j>plr$56rNZW*1VTW#U>F`QA;H%( z05$Xo@A~@J>G|Q-&cp#a4-Lfc7IJ*dDCP|y-FN96Ol|R^1Szz_2A8EgsDHWX!b+E< zztBiV3gd)46Y<4tQxMvdVB$!xZRxuRkQ)cHTJ?_QE)1~WDS=by8CZVu$UO1bi3 zSIzHBSBny9s~O23!f5B&jBUl3Ys5Wd=iow%Zg6T_zp;;h{lnYS!P>N|Q0UxJub$%K ztS#yD<97`ngcFjw{3ZJBpXlH$v|1%^yT4P6_$F%|8}_0(7bL%l?_m*=2p)3CK13@J zmk1a9p4qnFo$`3w@skdH$}v~_{ydk^_1S)1o6}!Kbh;PEt0iGAzWd2ldz&q`?=>wi;As-93#^Z~HHBg#jy8iC$>0V!2Sh2gi^UuS%pdXLZs(xSJtU0`UyW!}M?MPzX z9ynuCLI=>sf;umzTahYsYWor!N;3lbWa1h9`?NHp{qA|elE1u&;Li-G)iPVeUt|PC zS&j0kjl3bXi#G_FVv8}d75Z(a8^*uwxS}e~{>mtFk`6^4HCEc56-6x9)g8owr$m5# zF?g|nt%r*ljWZb6U~$)%mB~#sv>eOTw9=#8&@j7-?uY2>4?Dg+}{3H z($2n9@O|8Tr|^N#|DSgbny}8*&_>2^Y^A0g2?jixfz4=Mm~MGskNW5-PorV}X>v?{ z!yfmPu8PlE+9O@#2HS5ga3Tkkho={orQb)Sk0ToRQ}(t4OoH_|ePUdQk{uF1Llk{Y#&-F|vFOXL*tS zT?@6Y5W{1;Z@c7^`M{_*m|avm^CcMgnvuogl9yfDT>cx6T1^c5Xc2FNY#;mC_GpQ^ zzrmVdY-SLY>!BxfDm?#Hb<1YEFN~pizS`|}o$%u`z zwmiS*zi~OVrv13SZc&dY_HhxgD>u%&hw0Z9I^Qvm`TW^(uGaCetGoj}Xz)wFA@E_x z#>WJ~^))_tXIeqp@9Xo^QyjR*Urtg99ykayeLIiGye9y$YW!!3K6%O~=@z_i`DZO* zCb>&KRN|9SR&e#sB|qxGFXX#(&Jr|-wM9&a40C4u+<7VjSrnXHf) zsI(_$Q3XMQLx@3g6%v&&`!0(!6aIwmO$rKTjTt}IoZ)NOT03)N3Z2t zJEJ{yo#5*C?&EO3xu?Z+2cx1kAu67VDA_eB4M~-e6`ab9f#mz;6n-@*OE0b{_)(%< zllMI(7g1DK*-+I~-kEJ0jY9~;{TGhYP}s;AbkcQZ4mk>LhEG$^&Tkm3>}_uEt}Ptw ztRmJIrw&i|Zw}jPUn!>5qd_lr3GQ_+5WFTN3lA4nhGM|v*&*q$re)5M`^=8dagTu= z5iO8gV-96fbXnwsx9-MlDlAD?g-(`Vh2YuFSSDRr;9fT+3W&omWO9`gi5LXfz8Mb*IyXe)x&Atn zD>*OHZ}uJPKK-Ea>0ONn@JTF`x@4&$@kxHQZkscH2op_h3m(yMDwnC0XAHKF z*k7fo@tJ?l(h}&dLWaD(VV%PkF2YR-pCzR>R+aquCHtv7eb;Ivzd}yye}ah0C1w13 zPRv#;K}BPeH?>KS@XLgvqs{r)L`H_w|EeRY3f_43oJMs-vqN33yOJ^iL7Eq5c06|3*t{BM@6Q`NLoD>lSPmj;cPRWHNBg4LaLlh?FXOtDg zOVeUmpg5HrP;x>r^B)s3-=#XNwOy~LkEt!QySIOAXmW631Uz-e;q378!s<-d^!(x) zVzl#9GY zqg$i0di(P;C7<+R=^87jh09sB;CKVv5=tiI7hH9?+!aSD(*NAX)~GL<*?WC=*^8AJ z9ISFYt9okCf_ZD|o05o9ohq=JaR^9qILKD?RHXblZN1e%-@KhuJ8NYd?KBYk{*jv% zmd8knM&6@mLMPyX7ffOEbAk@8k1&BAB#J9t5xX*^ z6-$;k-Th+8J6+l1y6Y8CX;nEhSYc5ZB&JNMU~lk&TDGdu?}z;b&6m0W$2ou4x);Kq zJ(5+=oP~V_;lo`7GYr~f%UB70ezv(1CW_0p8ZJSFT8)rq`Mbi)K4_lhy%Qc<=1zD6 z5k^9sghj!T;zM`dGb|em_RFu|C~=T`-?Onc^dscm&I-!j!xdrYj&S)5*THgv%V}{# zp};^mK0a2AC1LDc_9oe;FsDH>eG^mDF+uq`1*mMM+(PBM*e&~(S6fn_Tvu8Ay;O8H6 zJ$@k71Lpz^caCf%uUO!92~VC(9MUv4(2P;)8RW4L`pVZX6AL64B@(ht!QPsS%+rf8 zr(ESLQZ0A5xvVgsOwz75loYCbKXnhnD)A9E+9VUsxHW&`cy6kffOS=Qt)s!k((D3! z2_?ZQF^~(Udu*xeSY})o@z7|zX9W1J2TVS_dmX#`Bv@oFm0nBRet0}wW5Fi{W#>=y zowAc9Aj{F&&mfn&pg~KpPUw&lm-=w&^cpm_1Akh6j2p8 zRL-_&SSf$S^6GJa^1vP7?0(bFfADOe;ZAs$7i~Fdrh%tVb z%13a@Z$>m%IetlkSbvkopI*w;!2cy9*f0R;hZp^c)%+sEdp$;j(#>3qYens8pd?a@ zV6}5R)biBPDCFhwW|lpOM$0(-)%+Q4_z?bAWT>nv>OYg0ofP|T1@G#<3PSfOp^84c zeq^%OrgT$1Nuq*O*Am97+{pJ*Q1tcK_)t2hyUTz*>Ag_J;xz;JPY7E}9oLV3aAR^0 zZf$>{69S(KE7FQE3QiRpACZ(ol;#|lk%~%W&dd(V{f0@%gA~5WXDawwUQt|CnqE@x z19+t5|4#&=cmKcPuAaW6Vd_z2FL+`EHa5pRIgOZ|pIV$5Us+h%T!U}yb#AZk4(;zO zUv3=?`ai936t(ukA|oTSIXZ1*5ch3$6>zOmT5cl6N2<~)r#DIjlRZ^Ac2t&-ib$5e zjYY!>R{f($1@g0DwyPa+9GoJfn!kV`2)348bY+=0v!(=`MhP7)K9nP$#`AttbxSot zMoc`1oiAUlSi3m1*L)Z=8EyQ@vTgc}88Zl9%zT%1#Avq6mKwJz=!w-_y%2Da4)yGC zG1_+xGTt-c4Q}wkdMKjH#y#KWfrOdV3D^c_bemOjB%WEAh3Am7S_eEiH6Iq1Z=vVZ zHnk|o>!~rB%V^o3s(h!vvvtupmC-5;1M4^lOcW2a@MyTyiB2z%6+awEofViV8cMSD z;FbSn+`l257xwWtkJ#z{P>275Mw81~%2Dwv4yT{)x7QUL%{T+zGI)tz#D@G!AE4UO8J6@mnv51m+y}E1i*BZ4ZucJnngUhpVN^k#U99vW z02wC2()hHz?wzX(g_5b0J{_oOPq&yXlHz@B- zfq^{AVa>``(oG-m)EC-zE?wfD_|!)>I$C~TFE+31^nmu3d*|ST1^Sa05@_hEM(+FA z!5--PM}Xm<2qUPdAhBTyNhyfHG@=Z*nC#pv=EUR=@dfF{U&~8#DwqoMF_kz^taJhY z3tlsIYkOxz-vF`$Jls5r7@ur`4Nf%mFN_T>Ps3+7R%h0Gr*^mY?dKQY9ITx6ynloZ zEVyeMckorLv zqBW93v1S)jX515u!_Bi}8sybVlw^o?m@tCI&X9OaBY9=Z)|??9CL(V>%qEYK%+brC z8QJc{(Tp*_p{d-SjDBC6D$j2e+3j_YuI%ROX`Zf+{R(b;5>IhgE$>Y`j@&a*oLZM7 z8mZoiJ;VBt2g1LT_zzc`VhCDtXfMTdWZP_VV`!aMJ8Vg+s1#-kp;C=wPr``Xvk;<$Lk zW!$zdkY!r;yKe0yUbw;Z#X`Nw)%o6eeXy>H%js#osHgbPdeud>#y#&vJx>gGe3Cbl zm}Znuv2r|vLbJgn9*BUIKgH3o>(w|mi7_)mn<hSR%p+)EA1 zE|cdR=q4PTRzvdUOiHc`8wOtJeROvO0{@4gbS; zo!!&pZ|BEmmmyP#nKwf#i)*_h3tLNvoAB-Rvy0Q?^Ks9|EYBUsdjRd+N=vpP}bvZ5AFulRsPIG+E%E-}Ib&gNf}4k?vbw=pvv< zy^R{$DQi73`AO0oNBK+t0kqH$TEdzW)c9A)a%j+Wr1^A-MUTQ3b!E^4R!RNc4 zrCcvwh!BmJg4f$pUjFEI7C6T4PJ1^X;$~~V*H_q(&rxl?+_Jus`A#?W=CbQErO=Oy z^cz>u=|Wyu!*6%@(?jM53q~iLyf1BvgaL#WR+Mb~Nw!2hUuL{>CC${l2=uCw{OXl2 z68)(nE0;`oo}=ITwO>-EpgL~A)7T_RYnfArzzSqA z&r>zyAXyev4B5X6?@#5G`kMr17nhR8<0VP4agpk#V*AN_s*c5-+Ut0Y{UPt|u2Z&k z@R-g+T6y|}6(4~BoqKcVM5uQLE-FAC0MzLX;Rhr>k$-R~f(jDp8x4<*{}{rOtdqu^ z5tSa7nV6H1mx55u$K+<06=VnUfIvWMW`G6kANArVHMEJT1=^18Y6d4Z4E7A`bao?q zC&y-5=cW<8{U4`>MmEN`=2s><_TaPJpy7I^8gksbl3|fed~);)yf`Z2erribJM@@Q z#1oHzop$q(RDz6*UA=}iGo#g?fZBYv^~n2`H^sqi6Bo%L1&ExEquK{0+9c=BXJyzc zv%{>AB#gpt12=B;XM_bp#=62EC}+t>sH)WtRtad=)FOx&Qpu1(swJBH%>n+iK}&9-O%ac zGFE>8H^}$j+c~cDJa_2bq!l=E?ae!PE1GrwTt6kRaz2w>ceWj)+o_JOZt=EBx~xB# z{p5}cK4a_gC#Es^eQ|O0Io<=?eQe(2X75#+tPGx|_)Dw?OzgDcro}M<4#k4EUR+(IR=sOj+?E7@ULV3Z^4lKyLg2)QRvFzH0O#z zG*gt=va<2?WxPH0b<}iI^|_r!g-tu&KhdU8@0de5UJ5~WrfjS}AS`U{9MGOlsz^Dk zClthRpe{Kf`JLW~qTk2eg(^CYDG`$CgO!cU#lY3?8f;7{ygb#rGUI#okF5IIs`R`% zW@11E2QHZE&!9K-Pt^?sjEqfG4^ht|$H7yB!=o$n?VEFJyGvV$)rGz7$;0Je^V2?O ze;xF<*=iIA)e*zEm|$ppzlukP&_7cO7Fq>FFBi`5Wr!2h4*AHTe1vF^pnHAmCy_w5 zDiex$USrHmW+I3G@a{;U0NUzu)kE@f@(uEsquF9Tk9M6Pro+l%orr?@=5V7FhxAX)~mg(e3A z`24qrhkXqJMAeu?l-2+@bcR|9rzc@cG;8yAAuh5rB(fcy|+MWi)ZNh*fL9KR=V&mrDB7 z@px^_Yi^#d{_<>LDX3vT)y)w`AmLDWonX7!cI!z+jRrzG;htcc$w^0(EG)`&50H3# zx=${5sfulfE`PFOH)@j6pY1&!!s_OGrGfc_#0$p2hxstWfD7M~c{jm6=>!K7N9HbP zftm`V5P}o_`MV9fGba3mR7)>h>UfeVT$;u|C2~!2swrC5_}?}?)Ov#1pL!wmUtSV{ AkpKVy literal 0 HcmV?d00001 diff --git a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php index 99778d4f2..3c7da31c5 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php @@ -5,6 +5,8 @@ namespace ProcessMaker\BusinessModel; use Exception; use G; use ProcessMaker\Model\Application; +use ProcessMaker\Model\Delegation; +use ProcessMaker\Model\Documents; use RBAC; use Tests\TestCase; @@ -77,4 +79,61 @@ class CasesTest extends TestCase $case = new Cases(); $case->deleteCase($application->APP_UID, $_SESSION['USER_LOGGED']); } + + /** + * Review the upload file related to the case notes, an return an exception when the array is empty + * + * @covers \ProcessMaker\BusinessModel\Cases::uploadFilesInCaseNotes() + * + * @test + * @expectedException Exception + */ + public function it_return_exception_in_upload_files_related_case_note() + { + $application = factory(Application::class)->create(); + factory(Delegation::class)->states('foreign_keys')->create([ + 'APP_NUMBER' => $application->APP_NUMBER, + 'APP_UID' => $application->APP_UID + ]); + // Upload the file + $case = new Cases(); + // Return an exception because the files does not exist + $case->uploadFilesInCaseNotes('00000000000000000000000000000001', $application->APP_UID, $filesReferences = []); + } + + /** + * Review the upload file related to the case notes + * + * @covers \ProcessMaker\BusinessModel\Cases::uploadFilesInCaseNotes() + * + * @test + */ + public function it_should_upload_files_related_case_note() + { + $application = factory(Application::class)->create(); + factory(Delegation::class)->states('foreign_keys')->create([ + 'APP_NUMBER' => $application->APP_NUMBER, + 'APP_UID' => $application->APP_UID + ]); + // File reference to upload + $filesReferences = [ + 'activityRename.gif' => PATH_TRUNK . 'tests' . PATH_SEP . 'resources' . PATH_SEP . 'images' . PATH_SEP . 'activity.gif', + ]; + // Path of the case + $pathCase = PATH_DB . config('system.workspace') . PATH_SEP . 'files' . PATH_SEP . $application->APP_UID . PATH_SEP; + // Upload the file + $case = new Cases(); + $result = $case->uploadFilesInCaseNotes('00000000000000000000000000000001', $application->APP_UID, $filesReferences); + $result = head($result); + $this->assertNotEmpty($result); + $this->assertArrayHasKey('APP_UID', $result); + $this->assertEquals($application->APP_UID, $result['APP_UID']); + $this->assertArrayHasKey('APP_DOC_TYPE', $result); + $this->assertEquals(Documents::DOC_TYPE_CASE_NOTE, $result['APP_DOC_TYPE']); + $this->assertArrayHasKey('APP_DOC_FILENAME', $result); + $this->assertEquals('activityRename.gif', $result['APP_DOC_FILENAME']); + + // Remove the path created + G::rm_dir($pathCase); + } } diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Model/DocumentsTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Model/DocumentsTest.php new file mode 100644 index 000000000..bc882b79d --- /dev/null +++ b/tests/unit/workflow/engine/src/ProcessMaker/Model/DocumentsTest.php @@ -0,0 +1,29 @@ +states('case_notes')->create(); + $doc = new Documents(); + $res = $doc->getAppFiles($appDoc->APP_UID, Documents::DOC_TYPE_CASE_NOTE); + $this->assertNotEmpty($res); + } +} \ No newline at end of file diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/SaveAppDocumentTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/SaveAppDocumentTest.php new file mode 100644 index 000000000..4f1079a08 --- /dev/null +++ b/tests/unit/workflow/engine/src/ProcessMaker/Util/Helpers/SaveAppDocumentTest.php @@ -0,0 +1,47 @@ + PATH_TRUNK . 'tests' . PATH_SEP . 'resources' . PATH_SEP . 'images' . PATH_SEP . 'activate.png', + 'tmp_name' => PATH_TRUNK . 'tests' . PATH_SEP . 'resources' . PATH_SEP . 'images' . PATH_SEP . 'activate.png', + ]; + $appUid = G::generateUniqueID(); + $appDocUid = G::generateUniqueID(); + $pathCase = PATH_DB . config('system.workspace') . PATH_SEP . 'files' . PATH_SEP . G::getPathFromUID($appUid) . PATH_SEP; + saveAppDocument($files, $appUid, $appDocUid, 1, false); + $this->assertFileExists($pathCase . $appDocUid . '_1.png'); + G::rm_dir($pathCase); + } + + /** + * It test if the file reference was uploaded + * + * @test + */ + public function it_should_copy_file_different_name() + { + $files = [ + 'name' => 'activityRename.gif', + 'tmp_name' => PATH_TRUNK . 'tests' . PATH_SEP . 'resources' . PATH_SEP . 'images' . PATH_SEP . 'activity.gif', + ]; + $appUid = G::generateUniqueID(); + $appDocUid = G::generateUniqueID(); + $pathCase = PATH_DB . config('system.workspace') . PATH_SEP . 'files' . PATH_SEP . G::getPathFromUID($appUid) . PATH_SEP; + saveAppDocument($files, $appUid, $appDocUid, 1, false); + $this->assertFileExists($pathCase . $appDocUid . '_1.gif'); + G::rm_dir($pathCase); + } +} \ No newline at end of file diff --git a/workflow/engine/classes/WsBase.php b/workflow/engine/classes/WsBase.php index 898b2af56..48330d1cd 100644 --- a/workflow/engine/classes/WsBase.php +++ b/workflow/engine/classes/WsBase.php @@ -3420,16 +3420,17 @@ class WsBase /** * Add case note * - * @param string caseUid : ID of the case. - * @param string processUid : ID of the process. - * @param string taskUid : ID of the task. - * @param string userUid : The unique ID of the user who will add note case. - * @param string note : Note of the case. - * @param int sendMail : Optional parameter. If set to 1, will send an email to all participants in the case. + * @param string $caseUid, ID of the case. + * @param string $processUid, ID of the process. + * @param string $taskUid, ID of the task. + * @param string $userUid, The unique ID of the user who will add note case. + * @param string $note, Note of the case. + * @param int $sendMail, Optional parameter. If set to 1, will send an email to all participants in the case. + * @param array $files, Optional parameter. This is an array of files. * - * @return $result will return an object + * @return object */ - public function addCaseNote($caseUid, $processUid, $taskUid, $userUid, $note, $sendMail = 1) + public function addCaseNote($caseUid, $processUid, $taskUid, $userUid, $note, $sendMail = 1, $files = []) { try { if (empty($caseUid)) { @@ -3474,8 +3475,8 @@ class WsBase } //Add note case - $appNote = new AppNotes(); - $response = $appNote->addCaseNote($caseUid, $userUid, $note, $sendMail); + $appNote = new ProcessMaker\BusinessModel\Cases(); + $response = $appNote->addNote($caseUid, $userUid, $note, $sendMail, $files); //Response $result = new WsResponse(0, G::LoadTranslation("ID_COMMAND_EXECUTED_SUCCESSFULLY")); diff --git a/workflow/engine/classes/class.pmFunctions.php b/workflow/engine/classes/class.pmFunctions.php index e99854804..5193afb66 100644 --- a/workflow/engine/classes/class.pmFunctions.php +++ b/workflow/engine/classes/class.pmFunctions.php @@ -2887,13 +2887,15 @@ function PMFUnpauseCase ($caseUid, $delIndex, $userUid) * @param string(32) | $userUid | ID user | The unique ID of the user who will add note case. * @param string | $note | Note of the case | Note of the case. * @param int | $sendMail = 1 | Send mail | Optional parameter. If set to 1, will send an email to all participants in the case. + * @param array | $files | Array of files | An array of files (full paths) to be attached to the case notes. + * * @return int | $result | Result of the add a case note | Returns 1 if the note has been added to the case.; otherwise, returns 0 if an error occurred. * */ -function PMFAddCaseNote($caseUid, $processUid, $taskUid, $userUid, $note, $sendMail = 1) +function PMFAddCaseNote($caseUid, $processUid, $taskUid, $userUid, $note, $sendMail = 1, $files = []) { $ws = new WsBase(); - $result = $ws->addCaseNote($caseUid, $processUid, $taskUid, $userUid, $note, $sendMail); + $result = $ws->addCaseNote($caseUid, $processUid, $taskUid, $userUid, $note, $sendMail, $files); if ($result->status_code == 0) { return 1; diff --git a/workflow/engine/classes/model/map/AppDocumentMapBuilder.php b/workflow/engine/classes/model/map/AppDocumentMapBuilder.php index c0a6495d2..9e8ca2f1a 100644 --- a/workflow/engine/classes/model/map/AppDocumentMapBuilder.php +++ b/workflow/engine/classes/model/map/AppDocumentMapBuilder.php @@ -81,6 +81,8 @@ class AppDocumentMapBuilder $tMap->addColumn('DOC_UID', 'DocUid', 'string', CreoleTypes::VARCHAR, true, 32); + $tMap->addColumn('DOC_ID', 'DocId', 'int', CreoleTypes::INTEGER, false, null); + $tMap->addColumn('USR_UID', 'UsrUid', 'string', CreoleTypes::VARCHAR, true, 32); $tMap->addColumn('APP_DOC_TYPE', 'AppDocType', 'string', CreoleTypes::VARCHAR, true, 32); @@ -127,7 +129,7 @@ class AppDocumentMapBuilder $tMap->addValidator('USR_UID', 'required', 'propel.validator.RequiredValidator', '', 'User UID is required.'); - $tMap->addValidator('APP_DOC_TYPE', 'validValues', 'propel.validator.ValidValuesValidator', 'INPUT|OUTPUT|ATTACHED', 'Please select a valid document type.'); + $tMap->addValidator('APP_DOC_TYPE', 'validValues', 'propel.validator.ValidValuesValidator', 'INPUT|OUTPUT|ATTACHED|CASE_NOTE', 'Please select a valid document type.'); $tMap->addValidator('APP_DOC_TYPE', 'required', 'propel.validator.RequiredValidator', '', 'Application Document Type is required.'); diff --git a/workflow/engine/classes/model/map/AppNotesMapBuilder.php b/workflow/engine/classes/model/map/AppNotesMapBuilder.php index 469e66cda..90f497a0b 100644 --- a/workflow/engine/classes/model/map/AppNotesMapBuilder.php +++ b/workflow/engine/classes/model/map/AppNotesMapBuilder.php @@ -63,7 +63,9 @@ class AppNotesMapBuilder $tMap = $this->dbMap->addTable('APP_NOTES'); $tMap->setPhpName('AppNotes'); - $tMap->setUseIdGenerator(false); + $tMap->setUseIdGenerator(true); + + $tMap->addColumn('NOTE_ID', 'NoteId', 'int', CreoleTypes::INTEGER, true, null); $tMap->addColumn('APP_UID', 'AppUid', 'string', CreoleTypes::VARCHAR, true, 32); diff --git a/workflow/engine/classes/model/om/BaseAppDocument.php b/workflow/engine/classes/model/om/BaseAppDocument.php index d4874f670..ce2d5a86c 100644 --- a/workflow/engine/classes/model/om/BaseAppDocument.php +++ b/workflow/engine/classes/model/om/BaseAppDocument.php @@ -75,6 +75,12 @@ abstract class BaseAppDocument extends BaseObject implements Persistent */ protected $doc_uid = ''; + /** + * The value for the doc_id field. + * @var int + */ + protected $doc_id = 0; + /** * The value for the usr_uid field. * @var string @@ -255,6 +261,17 @@ abstract class BaseAppDocument extends BaseObject implements Persistent return $this->doc_uid; } + /** + * Get the [doc_id] column value. + * + * @return int + */ + public function getDocId() + { + + return $this->doc_id; + } + /** * Get the [usr_uid] column value. * @@ -616,6 +633,28 @@ abstract class BaseAppDocument extends BaseObject implements Persistent } // setDocUid() + /** + * Set the value of [doc_id] column. + * + * @param int $v new value + * @return void + */ + public function setDocId($v) + { + + // Since the native PHP type for this column is integer, + // we will cast the input value to an int (if it is not). + if ($v !== null && !is_int($v) && is_numeric($v)) { + $v = (int) $v; + } + + if ($this->doc_id !== $v || $v === 0) { + $this->doc_id = $v; + $this->modifiedColumns[] = AppDocumentPeer::DOC_ID; + } + + } // setDocId() + /** * Set the value of [usr_uid] column. * @@ -949,38 +988,40 @@ abstract class BaseAppDocument extends BaseObject implements Persistent $this->doc_uid = $rs->getString($startcol + 7); - $this->usr_uid = $rs->getString($startcol + 8); + $this->doc_id = $rs->getInt($startcol + 8); - $this->app_doc_type = $rs->getString($startcol + 9); + $this->usr_uid = $rs->getString($startcol + 9); - $this->app_doc_create_date = $rs->getTimestamp($startcol + 10, null); + $this->app_doc_type = $rs->getString($startcol + 10); - $this->app_doc_index = $rs->getInt($startcol + 11); + $this->app_doc_create_date = $rs->getTimestamp($startcol + 11, null); - $this->folder_uid = $rs->getString($startcol + 12); + $this->app_doc_index = $rs->getInt($startcol + 12); - $this->app_doc_plugin = $rs->getString($startcol + 13); + $this->folder_uid = $rs->getString($startcol + 13); - $this->app_doc_tags = $rs->getString($startcol + 14); + $this->app_doc_plugin = $rs->getString($startcol + 14); - $this->app_doc_status = $rs->getString($startcol + 15); + $this->app_doc_tags = $rs->getString($startcol + 15); - $this->app_doc_status_date = $rs->getTimestamp($startcol + 16, null); + $this->app_doc_status = $rs->getString($startcol + 16); - $this->app_doc_fieldname = $rs->getString($startcol + 17); + $this->app_doc_status_date = $rs->getTimestamp($startcol + 17, null); - $this->app_doc_drive_download = $rs->getString($startcol + 18); + $this->app_doc_fieldname = $rs->getString($startcol + 18); - $this->sync_with_drive = $rs->getString($startcol + 19); + $this->app_doc_drive_download = $rs->getString($startcol + 19); - $this->sync_permissions = $rs->getString($startcol + 20); + $this->sync_with_drive = $rs->getString($startcol + 20); + + $this->sync_permissions = $rs->getString($startcol + 21); $this->resetModified(); $this->setNew(false); // FIXME - using NUM_COLUMNS may be clearer. - return $startcol + 21; // 21 = AppDocumentPeer::NUM_COLUMNS - AppDocumentPeer::NUM_LAZY_LOAD_COLUMNS). + return $startcol + 22; // 22 = AppDocumentPeer::NUM_COLUMNS - AppDocumentPeer::NUM_LAZY_LOAD_COLUMNS). } catch (Exception $e) { throw new PropelException("Error populating AppDocument object", $e); @@ -1209,42 +1250,45 @@ abstract class BaseAppDocument extends BaseObject implements Persistent return $this->getDocUid(); break; case 8: - return $this->getUsrUid(); + return $this->getDocId(); break; case 9: - return $this->getAppDocType(); + return $this->getUsrUid(); break; case 10: - return $this->getAppDocCreateDate(); + return $this->getAppDocType(); break; case 11: - return $this->getAppDocIndex(); + return $this->getAppDocCreateDate(); break; case 12: - return $this->getFolderUid(); + return $this->getAppDocIndex(); break; case 13: - return $this->getAppDocPlugin(); + return $this->getFolderUid(); break; case 14: - return $this->getAppDocTags(); + return $this->getAppDocPlugin(); break; case 15: - return $this->getAppDocStatus(); + return $this->getAppDocTags(); break; case 16: - return $this->getAppDocStatusDate(); + return $this->getAppDocStatus(); break; case 17: - return $this->getAppDocFieldname(); + return $this->getAppDocStatusDate(); break; case 18: - return $this->getAppDocDriveDownload(); + return $this->getAppDocFieldname(); break; case 19: - return $this->getSyncWithDrive(); + return $this->getAppDocDriveDownload(); break; case 20: + return $this->getSyncWithDrive(); + break; + case 21: return $this->getSyncPermissions(); break; default: @@ -1275,19 +1319,20 @@ abstract class BaseAppDocument extends BaseObject implements Persistent $keys[5] => $this->getAppUid(), $keys[6] => $this->getDelIndex(), $keys[7] => $this->getDocUid(), - $keys[8] => $this->getUsrUid(), - $keys[9] => $this->getAppDocType(), - $keys[10] => $this->getAppDocCreateDate(), - $keys[11] => $this->getAppDocIndex(), - $keys[12] => $this->getFolderUid(), - $keys[13] => $this->getAppDocPlugin(), - $keys[14] => $this->getAppDocTags(), - $keys[15] => $this->getAppDocStatus(), - $keys[16] => $this->getAppDocStatusDate(), - $keys[17] => $this->getAppDocFieldname(), - $keys[18] => $this->getAppDocDriveDownload(), - $keys[19] => $this->getSyncWithDrive(), - $keys[20] => $this->getSyncPermissions(), + $keys[8] => $this->getDocId(), + $keys[9] => $this->getUsrUid(), + $keys[10] => $this->getAppDocType(), + $keys[11] => $this->getAppDocCreateDate(), + $keys[12] => $this->getAppDocIndex(), + $keys[13] => $this->getFolderUid(), + $keys[14] => $this->getAppDocPlugin(), + $keys[15] => $this->getAppDocTags(), + $keys[16] => $this->getAppDocStatus(), + $keys[17] => $this->getAppDocStatusDate(), + $keys[18] => $this->getAppDocFieldname(), + $keys[19] => $this->getAppDocDriveDownload(), + $keys[20] => $this->getSyncWithDrive(), + $keys[21] => $this->getSyncPermissions(), ); return $result; } @@ -1344,42 +1389,45 @@ abstract class BaseAppDocument extends BaseObject implements Persistent $this->setDocUid($value); break; case 8: - $this->setUsrUid($value); + $this->setDocId($value); break; case 9: - $this->setAppDocType($value); + $this->setUsrUid($value); break; case 10: - $this->setAppDocCreateDate($value); + $this->setAppDocType($value); break; case 11: - $this->setAppDocIndex($value); + $this->setAppDocCreateDate($value); break; case 12: - $this->setFolderUid($value); + $this->setAppDocIndex($value); break; case 13: - $this->setAppDocPlugin($value); + $this->setFolderUid($value); break; case 14: - $this->setAppDocTags($value); + $this->setAppDocPlugin($value); break; case 15: - $this->setAppDocStatus($value); + $this->setAppDocTags($value); break; case 16: - $this->setAppDocStatusDate($value); + $this->setAppDocStatus($value); break; case 17: - $this->setAppDocFieldname($value); + $this->setAppDocStatusDate($value); break; case 18: - $this->setAppDocDriveDownload($value); + $this->setAppDocFieldname($value); break; case 19: - $this->setSyncWithDrive($value); + $this->setAppDocDriveDownload($value); break; case 20: + $this->setSyncWithDrive($value); + break; + case 21: $this->setSyncPermissions($value); break; } // switch() @@ -1438,55 +1486,59 @@ abstract class BaseAppDocument extends BaseObject implements Persistent } if (array_key_exists($keys[8], $arr)) { - $this->setUsrUid($arr[$keys[8]]); + $this->setDocId($arr[$keys[8]]); } if (array_key_exists($keys[9], $arr)) { - $this->setAppDocType($arr[$keys[9]]); + $this->setUsrUid($arr[$keys[9]]); } if (array_key_exists($keys[10], $arr)) { - $this->setAppDocCreateDate($arr[$keys[10]]); + $this->setAppDocType($arr[$keys[10]]); } if (array_key_exists($keys[11], $arr)) { - $this->setAppDocIndex($arr[$keys[11]]); + $this->setAppDocCreateDate($arr[$keys[11]]); } if (array_key_exists($keys[12], $arr)) { - $this->setFolderUid($arr[$keys[12]]); + $this->setAppDocIndex($arr[$keys[12]]); } if (array_key_exists($keys[13], $arr)) { - $this->setAppDocPlugin($arr[$keys[13]]); + $this->setFolderUid($arr[$keys[13]]); } if (array_key_exists($keys[14], $arr)) { - $this->setAppDocTags($arr[$keys[14]]); + $this->setAppDocPlugin($arr[$keys[14]]); } if (array_key_exists($keys[15], $arr)) { - $this->setAppDocStatus($arr[$keys[15]]); + $this->setAppDocTags($arr[$keys[15]]); } if (array_key_exists($keys[16], $arr)) { - $this->setAppDocStatusDate($arr[$keys[16]]); + $this->setAppDocStatus($arr[$keys[16]]); } if (array_key_exists($keys[17], $arr)) { - $this->setAppDocFieldname($arr[$keys[17]]); + $this->setAppDocStatusDate($arr[$keys[17]]); } if (array_key_exists($keys[18], $arr)) { - $this->setAppDocDriveDownload($arr[$keys[18]]); + $this->setAppDocFieldname($arr[$keys[18]]); } if (array_key_exists($keys[19], $arr)) { - $this->setSyncWithDrive($arr[$keys[19]]); + $this->setAppDocDriveDownload($arr[$keys[19]]); } if (array_key_exists($keys[20], $arr)) { - $this->setSyncPermissions($arr[$keys[20]]); + $this->setSyncWithDrive($arr[$keys[20]]); + } + + if (array_key_exists($keys[21], $arr)) { + $this->setSyncPermissions($arr[$keys[21]]); } } @@ -1532,6 +1584,10 @@ abstract class BaseAppDocument extends BaseObject implements Persistent $criteria->add(AppDocumentPeer::DOC_UID, $this->doc_uid); } + if ($this->isColumnModified(AppDocumentPeer::DOC_ID)) { + $criteria->add(AppDocumentPeer::DOC_ID, $this->doc_id); + } + if ($this->isColumnModified(AppDocumentPeer::USR_UID)) { $criteria->add(AppDocumentPeer::USR_UID, $this->usr_uid); } @@ -1662,6 +1718,8 @@ abstract class BaseAppDocument extends BaseObject implements Persistent $copyObj->setDocUid($this->doc_uid); + $copyObj->setDocId($this->doc_id); + $copyObj->setUsrUid($this->usr_uid); $copyObj->setAppDocType($this->app_doc_type); diff --git a/workflow/engine/classes/model/om/BaseAppDocumentPeer.php b/workflow/engine/classes/model/om/BaseAppDocumentPeer.php index df748ac54..4aaeb0c1d 100644 --- a/workflow/engine/classes/model/om/BaseAppDocumentPeer.php +++ b/workflow/engine/classes/model/om/BaseAppDocumentPeer.php @@ -25,7 +25,7 @@ abstract class BaseAppDocumentPeer const CLASS_DEFAULT = 'classes.model.AppDocument'; /** The total number of columns. */ - const NUM_COLUMNS = 21; + const NUM_COLUMNS = 22; /** The number of lazy-loaded columns. */ const NUM_LAZY_LOAD_COLUMNS = 0; @@ -55,6 +55,9 @@ abstract class BaseAppDocumentPeer /** the column name for the DOC_UID field */ const DOC_UID = 'APP_DOCUMENT.DOC_UID'; + /** the column name for the DOC_ID field */ + const DOC_ID = 'APP_DOCUMENT.DOC_ID'; + /** the column name for the USR_UID field */ const USR_UID = 'APP_DOCUMENT.USR_UID'; @@ -105,10 +108,10 @@ abstract class BaseAppDocumentPeer * e.g. self::$fieldNames[self::TYPE_PHPNAME][0] = 'Id' */ private static $fieldNames = array ( - BasePeer::TYPE_PHPNAME => array ('AppDocUid', 'AppDocFilename', 'AppDocTitle', 'AppDocComment', 'DocVersion', 'AppUid', 'DelIndex', 'DocUid', 'UsrUid', 'AppDocType', 'AppDocCreateDate', 'AppDocIndex', 'FolderUid', 'AppDocPlugin', 'AppDocTags', 'AppDocStatus', 'AppDocStatusDate', 'AppDocFieldname', 'AppDocDriveDownload', 'SyncWithDrive', 'SyncPermissions', ), - BasePeer::TYPE_COLNAME => array (AppDocumentPeer::APP_DOC_UID, AppDocumentPeer::APP_DOC_FILENAME, AppDocumentPeer::APP_DOC_TITLE, AppDocumentPeer::APP_DOC_COMMENT, AppDocumentPeer::DOC_VERSION, AppDocumentPeer::APP_UID, AppDocumentPeer::DEL_INDEX, AppDocumentPeer::DOC_UID, AppDocumentPeer::USR_UID, AppDocumentPeer::APP_DOC_TYPE, AppDocumentPeer::APP_DOC_CREATE_DATE, AppDocumentPeer::APP_DOC_INDEX, AppDocumentPeer::FOLDER_UID, AppDocumentPeer::APP_DOC_PLUGIN, AppDocumentPeer::APP_DOC_TAGS, AppDocumentPeer::APP_DOC_STATUS, AppDocumentPeer::APP_DOC_STATUS_DATE, AppDocumentPeer::APP_DOC_FIELDNAME, AppDocumentPeer::APP_DOC_DRIVE_DOWNLOAD, AppDocumentPeer::SYNC_WITH_DRIVE, AppDocumentPeer::SYNC_PERMISSIONS, ), - BasePeer::TYPE_FIELDNAME => array ('APP_DOC_UID', 'APP_DOC_FILENAME', 'APP_DOC_TITLE', 'APP_DOC_COMMENT', 'DOC_VERSION', 'APP_UID', 'DEL_INDEX', 'DOC_UID', 'USR_UID', 'APP_DOC_TYPE', 'APP_DOC_CREATE_DATE', 'APP_DOC_INDEX', 'FOLDER_UID', 'APP_DOC_PLUGIN', 'APP_DOC_TAGS', 'APP_DOC_STATUS', 'APP_DOC_STATUS_DATE', 'APP_DOC_FIELDNAME', 'APP_DOC_DRIVE_DOWNLOAD', 'SYNC_WITH_DRIVE', 'SYNC_PERMISSIONS', ), - BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ) + BasePeer::TYPE_PHPNAME => array ('AppDocUid', 'AppDocFilename', 'AppDocTitle', 'AppDocComment', 'DocVersion', 'AppUid', 'DelIndex', 'DocUid', 'DocId', 'UsrUid', 'AppDocType', 'AppDocCreateDate', 'AppDocIndex', 'FolderUid', 'AppDocPlugin', 'AppDocTags', 'AppDocStatus', 'AppDocStatusDate', 'AppDocFieldname', 'AppDocDriveDownload', 'SyncWithDrive', 'SyncPermissions', ), + BasePeer::TYPE_COLNAME => array (AppDocumentPeer::APP_DOC_UID, AppDocumentPeer::APP_DOC_FILENAME, AppDocumentPeer::APP_DOC_TITLE, AppDocumentPeer::APP_DOC_COMMENT, AppDocumentPeer::DOC_VERSION, AppDocumentPeer::APP_UID, AppDocumentPeer::DEL_INDEX, AppDocumentPeer::DOC_UID, AppDocumentPeer::DOC_ID, AppDocumentPeer::USR_UID, AppDocumentPeer::APP_DOC_TYPE, AppDocumentPeer::APP_DOC_CREATE_DATE, AppDocumentPeer::APP_DOC_INDEX, AppDocumentPeer::FOLDER_UID, AppDocumentPeer::APP_DOC_PLUGIN, AppDocumentPeer::APP_DOC_TAGS, AppDocumentPeer::APP_DOC_STATUS, AppDocumentPeer::APP_DOC_STATUS_DATE, AppDocumentPeer::APP_DOC_FIELDNAME, AppDocumentPeer::APP_DOC_DRIVE_DOWNLOAD, AppDocumentPeer::SYNC_WITH_DRIVE, AppDocumentPeer::SYNC_PERMISSIONS, ), + BasePeer::TYPE_FIELDNAME => array ('APP_DOC_UID', 'APP_DOC_FILENAME', 'APP_DOC_TITLE', 'APP_DOC_COMMENT', 'DOC_VERSION', 'APP_UID', 'DEL_INDEX', 'DOC_UID', 'DOC_ID', 'USR_UID', 'APP_DOC_TYPE', 'APP_DOC_CREATE_DATE', 'APP_DOC_INDEX', 'FOLDER_UID', 'APP_DOC_PLUGIN', 'APP_DOC_TAGS', 'APP_DOC_STATUS', 'APP_DOC_STATUS_DATE', 'APP_DOC_FIELDNAME', 'APP_DOC_DRIVE_DOWNLOAD', 'SYNC_WITH_DRIVE', 'SYNC_PERMISSIONS', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, ) ); /** @@ -118,10 +121,10 @@ abstract class BaseAppDocumentPeer * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 */ private static $fieldKeys = array ( - BasePeer::TYPE_PHPNAME => array ('AppDocUid' => 0, 'AppDocFilename' => 1, 'AppDocTitle' => 2, 'AppDocComment' => 3, 'DocVersion' => 4, 'AppUid' => 5, 'DelIndex' => 6, 'DocUid' => 7, 'UsrUid' => 8, 'AppDocType' => 9, 'AppDocCreateDate' => 10, 'AppDocIndex' => 11, 'FolderUid' => 12, 'AppDocPlugin' => 13, 'AppDocTags' => 14, 'AppDocStatus' => 15, 'AppDocStatusDate' => 16, 'AppDocFieldname' => 17, 'AppDocDriveDownload' => 18, 'SyncWithDrive' => 19, 'SyncPermissions' => 20, ), - BasePeer::TYPE_COLNAME => array (AppDocumentPeer::APP_DOC_UID => 0, AppDocumentPeer::APP_DOC_FILENAME => 1, AppDocumentPeer::APP_DOC_TITLE => 2, AppDocumentPeer::APP_DOC_COMMENT => 3, AppDocumentPeer::DOC_VERSION => 4, AppDocumentPeer::APP_UID => 5, AppDocumentPeer::DEL_INDEX => 6, AppDocumentPeer::DOC_UID => 7, AppDocumentPeer::USR_UID => 8, AppDocumentPeer::APP_DOC_TYPE => 9, AppDocumentPeer::APP_DOC_CREATE_DATE => 10, AppDocumentPeer::APP_DOC_INDEX => 11, AppDocumentPeer::FOLDER_UID => 12, AppDocumentPeer::APP_DOC_PLUGIN => 13, AppDocumentPeer::APP_DOC_TAGS => 14, AppDocumentPeer::APP_DOC_STATUS => 15, AppDocumentPeer::APP_DOC_STATUS_DATE => 16, AppDocumentPeer::APP_DOC_FIELDNAME => 17, AppDocumentPeer::APP_DOC_DRIVE_DOWNLOAD => 18, AppDocumentPeer::SYNC_WITH_DRIVE => 19, AppDocumentPeer::SYNC_PERMISSIONS => 20, ), - BasePeer::TYPE_FIELDNAME => array ('APP_DOC_UID' => 0, 'APP_DOC_FILENAME' => 1, 'APP_DOC_TITLE' => 2, 'APP_DOC_COMMENT' => 3, 'DOC_VERSION' => 4, 'APP_UID' => 5, 'DEL_INDEX' => 6, 'DOC_UID' => 7, 'USR_UID' => 8, 'APP_DOC_TYPE' => 9, 'APP_DOC_CREATE_DATE' => 10, 'APP_DOC_INDEX' => 11, 'FOLDER_UID' => 12, 'APP_DOC_PLUGIN' => 13, 'APP_DOC_TAGS' => 14, 'APP_DOC_STATUS' => 15, 'APP_DOC_STATUS_DATE' => 16, 'APP_DOC_FIELDNAME' => 17, 'APP_DOC_DRIVE_DOWNLOAD' => 18, 'SYNC_WITH_DRIVE' => 19, 'SYNC_PERMISSIONS' => 20, ), - BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ) + BasePeer::TYPE_PHPNAME => array ('AppDocUid' => 0, 'AppDocFilename' => 1, 'AppDocTitle' => 2, 'AppDocComment' => 3, 'DocVersion' => 4, 'AppUid' => 5, 'DelIndex' => 6, 'DocUid' => 7, 'DocId' => 8, 'UsrUid' => 9, 'AppDocType' => 10, 'AppDocCreateDate' => 11, 'AppDocIndex' => 12, 'FolderUid' => 13, 'AppDocPlugin' => 14, 'AppDocTags' => 15, 'AppDocStatus' => 16, 'AppDocStatusDate' => 17, 'AppDocFieldname' => 18, 'AppDocDriveDownload' => 19, 'SyncWithDrive' => 20, 'SyncPermissions' => 21, ), + BasePeer::TYPE_COLNAME => array (AppDocumentPeer::APP_DOC_UID => 0, AppDocumentPeer::APP_DOC_FILENAME => 1, AppDocumentPeer::APP_DOC_TITLE => 2, AppDocumentPeer::APP_DOC_COMMENT => 3, AppDocumentPeer::DOC_VERSION => 4, AppDocumentPeer::APP_UID => 5, AppDocumentPeer::DEL_INDEX => 6, AppDocumentPeer::DOC_UID => 7, AppDocumentPeer::DOC_ID => 8, AppDocumentPeer::USR_UID => 9, AppDocumentPeer::APP_DOC_TYPE => 10, AppDocumentPeer::APP_DOC_CREATE_DATE => 11, AppDocumentPeer::APP_DOC_INDEX => 12, AppDocumentPeer::FOLDER_UID => 13, AppDocumentPeer::APP_DOC_PLUGIN => 14, AppDocumentPeer::APP_DOC_TAGS => 15, AppDocumentPeer::APP_DOC_STATUS => 16, AppDocumentPeer::APP_DOC_STATUS_DATE => 17, AppDocumentPeer::APP_DOC_FIELDNAME => 18, AppDocumentPeer::APP_DOC_DRIVE_DOWNLOAD => 19, AppDocumentPeer::SYNC_WITH_DRIVE => 20, AppDocumentPeer::SYNC_PERMISSIONS => 21, ), + BasePeer::TYPE_FIELDNAME => array ('APP_DOC_UID' => 0, 'APP_DOC_FILENAME' => 1, 'APP_DOC_TITLE' => 2, 'APP_DOC_COMMENT' => 3, 'DOC_VERSION' => 4, 'APP_UID' => 5, 'DEL_INDEX' => 6, 'DOC_UID' => 7, 'DOC_ID' => 8, 'USR_UID' => 9, 'APP_DOC_TYPE' => 10, 'APP_DOC_CREATE_DATE' => 11, 'APP_DOC_INDEX' => 12, 'FOLDER_UID' => 13, 'APP_DOC_PLUGIN' => 14, 'APP_DOC_TAGS' => 15, 'APP_DOC_STATUS' => 16, 'APP_DOC_STATUS_DATE' => 17, 'APP_DOC_FIELDNAME' => 18, 'APP_DOC_DRIVE_DOWNLOAD' => 19, 'SYNC_WITH_DRIVE' => 20, 'SYNC_PERMISSIONS' => 21, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, ) ); /** @@ -238,6 +241,8 @@ abstract class BaseAppDocumentPeer $criteria->addSelectColumn(AppDocumentPeer::DOC_UID); + $criteria->addSelectColumn(AppDocumentPeer::DOC_ID); + $criteria->addSelectColumn(AppDocumentPeer::USR_UID); $criteria->addSelectColumn(AppDocumentPeer::APP_DOC_TYPE); diff --git a/workflow/engine/classes/model/om/BaseAppNotes.php b/workflow/engine/classes/model/om/BaseAppNotes.php index 60225d6d6..4eba38105 100644 --- a/workflow/engine/classes/model/om/BaseAppNotes.php +++ b/workflow/engine/classes/model/om/BaseAppNotes.php @@ -27,6 +27,12 @@ abstract class BaseAppNotes extends BaseObject implements Persistent */ protected static $peer; + /** + * The value for the note_id field. + * @var int + */ + protected $note_id; + /** * The value for the app_uid field. * @var string @@ -101,6 +107,17 @@ abstract class BaseAppNotes extends BaseObject implements Persistent */ protected $alreadyInValidation = false; + /** + * Get the [note_id] column value. + * + * @return int + */ + public function getNoteId() + { + + return $this->note_id; + } + /** * Get the [app_uid] column value. * @@ -232,6 +249,28 @@ abstract class BaseAppNotes extends BaseObject implements Persistent return $this->note_recipients; } + /** + * Set the value of [note_id] column. + * + * @param int $v new value + * @return void + */ + public function setNoteId($v) + { + + // Since the native PHP type for this column is integer, + // we will cast the input value to an int (if it is not). + if ($v !== null && !is_int($v) && is_numeric($v)) { + $v = (int) $v; + } + + if ($this->note_id !== $v) { + $this->note_id = $v; + $this->modifiedColumns[] = AppNotesPeer::NOTE_ID; + } + + } // setNoteId() + /** * Set the value of [app_uid] column. * @@ -476,32 +515,34 @@ abstract class BaseAppNotes extends BaseObject implements Persistent { try { - $this->app_uid = $rs->getString($startcol + 0); + $this->note_id = $rs->getInt($startcol + 0); - $this->usr_uid = $rs->getString($startcol + 1); + $this->app_uid = $rs->getString($startcol + 1); - $this->note_date = $rs->getTimestamp($startcol + 2, null); + $this->usr_uid = $rs->getString($startcol + 2); - $this->note_content = $rs->getString($startcol + 3); + $this->note_date = $rs->getTimestamp($startcol + 3, null); - $this->note_type = $rs->getString($startcol + 4); + $this->note_content = $rs->getString($startcol + 4); - $this->note_availability = $rs->getString($startcol + 5); + $this->note_type = $rs->getString($startcol + 5); - $this->note_origin_obj = $rs->getString($startcol + 6); + $this->note_availability = $rs->getString($startcol + 6); - $this->note_affected_obj1 = $rs->getString($startcol + 7); + $this->note_origin_obj = $rs->getString($startcol + 7); - $this->note_affected_obj2 = $rs->getString($startcol + 8); + $this->note_affected_obj1 = $rs->getString($startcol + 8); - $this->note_recipients = $rs->getString($startcol + 9); + $this->note_affected_obj2 = $rs->getString($startcol + 9); + + $this->note_recipients = $rs->getString($startcol + 10); $this->resetModified(); $this->setNew(false); // FIXME - using NUM_COLUMNS may be clearer. - return $startcol + 10; // 10 = AppNotesPeer::NUM_COLUMNS - AppNotesPeer::NUM_LAZY_LOAD_COLUMNS). + return $startcol + 11; // 11 = AppNotesPeer::NUM_COLUMNS - AppNotesPeer::NUM_LAZY_LOAD_COLUMNS). } catch (Exception $e) { throw new PropelException("Error populating AppNotes object", $e); @@ -706,33 +747,36 @@ abstract class BaseAppNotes extends BaseObject implements Persistent { switch($pos) { case 0: - return $this->getAppUid(); + return $this->getNoteId(); break; case 1: - return $this->getUsrUid(); + return $this->getAppUid(); break; case 2: - return $this->getNoteDate(); + return $this->getUsrUid(); break; case 3: - return $this->getNoteContent(); + return $this->getNoteDate(); break; case 4: - return $this->getNoteType(); + return $this->getNoteContent(); break; case 5: - return $this->getNoteAvailability(); + return $this->getNoteType(); break; case 6: - return $this->getNoteOriginObj(); + return $this->getNoteAvailability(); break; case 7: - return $this->getNoteAffectedObj1(); + return $this->getNoteOriginObj(); break; case 8: - return $this->getNoteAffectedObj2(); + return $this->getNoteAffectedObj1(); break; case 9: + return $this->getNoteAffectedObj2(); + break; + case 10: return $this->getNoteRecipients(); break; default: @@ -755,16 +799,17 @@ abstract class BaseAppNotes extends BaseObject implements Persistent { $keys = AppNotesPeer::getFieldNames($keyType); $result = array( - $keys[0] => $this->getAppUid(), - $keys[1] => $this->getUsrUid(), - $keys[2] => $this->getNoteDate(), - $keys[3] => $this->getNoteContent(), - $keys[4] => $this->getNoteType(), - $keys[5] => $this->getNoteAvailability(), - $keys[6] => $this->getNoteOriginObj(), - $keys[7] => $this->getNoteAffectedObj1(), - $keys[8] => $this->getNoteAffectedObj2(), - $keys[9] => $this->getNoteRecipients(), + $keys[0] => $this->getNoteId(), + $keys[1] => $this->getAppUid(), + $keys[2] => $this->getUsrUid(), + $keys[3] => $this->getNoteDate(), + $keys[4] => $this->getNoteContent(), + $keys[5] => $this->getNoteType(), + $keys[6] => $this->getNoteAvailability(), + $keys[7] => $this->getNoteOriginObj(), + $keys[8] => $this->getNoteAffectedObj1(), + $keys[9] => $this->getNoteAffectedObj2(), + $keys[10] => $this->getNoteRecipients(), ); return $result; } @@ -797,33 +842,36 @@ abstract class BaseAppNotes extends BaseObject implements Persistent { switch($pos) { case 0: - $this->setAppUid($value); + $this->setNoteId($value); break; case 1: - $this->setUsrUid($value); + $this->setAppUid($value); break; case 2: - $this->setNoteDate($value); + $this->setUsrUid($value); break; case 3: - $this->setNoteContent($value); + $this->setNoteDate($value); break; case 4: - $this->setNoteType($value); + $this->setNoteContent($value); break; case 5: - $this->setNoteAvailability($value); + $this->setNoteType($value); break; case 6: - $this->setNoteOriginObj($value); + $this->setNoteAvailability($value); break; case 7: - $this->setNoteAffectedObj1($value); + $this->setNoteOriginObj($value); break; case 8: - $this->setNoteAffectedObj2($value); + $this->setNoteAffectedObj1($value); break; case 9: + $this->setNoteAffectedObj2($value); + break; + case 10: $this->setNoteRecipients($value); break; } // switch() @@ -850,43 +898,47 @@ abstract class BaseAppNotes extends BaseObject implements Persistent $keys = AppNotesPeer::getFieldNames($keyType); if (array_key_exists($keys[0], $arr)) { - $this->setAppUid($arr[$keys[0]]); + $this->setNoteId($arr[$keys[0]]); } if (array_key_exists($keys[1], $arr)) { - $this->setUsrUid($arr[$keys[1]]); + $this->setAppUid($arr[$keys[1]]); } if (array_key_exists($keys[2], $arr)) { - $this->setNoteDate($arr[$keys[2]]); + $this->setUsrUid($arr[$keys[2]]); } if (array_key_exists($keys[3], $arr)) { - $this->setNoteContent($arr[$keys[3]]); + $this->setNoteDate($arr[$keys[3]]); } if (array_key_exists($keys[4], $arr)) { - $this->setNoteType($arr[$keys[4]]); + $this->setNoteContent($arr[$keys[4]]); } if (array_key_exists($keys[5], $arr)) { - $this->setNoteAvailability($arr[$keys[5]]); + $this->setNoteType($arr[$keys[5]]); } if (array_key_exists($keys[6], $arr)) { - $this->setNoteOriginObj($arr[$keys[6]]); + $this->setNoteAvailability($arr[$keys[6]]); } if (array_key_exists($keys[7], $arr)) { - $this->setNoteAffectedObj1($arr[$keys[7]]); + $this->setNoteOriginObj($arr[$keys[7]]); } if (array_key_exists($keys[8], $arr)) { - $this->setNoteAffectedObj2($arr[$keys[8]]); + $this->setNoteAffectedObj1($arr[$keys[8]]); } if (array_key_exists($keys[9], $arr)) { - $this->setNoteRecipients($arr[$keys[9]]); + $this->setNoteAffectedObj2($arr[$keys[9]]); + } + + if (array_key_exists($keys[10], $arr)) { + $this->setNoteRecipients($arr[$keys[10]]); } } @@ -900,6 +952,10 @@ abstract class BaseAppNotes extends BaseObject implements Persistent { $criteria = new Criteria(AppNotesPeer::DATABASE_NAME); + if ($this->isColumnModified(AppNotesPeer::NOTE_ID)) { + $criteria->add(AppNotesPeer::NOTE_ID, $this->note_id); + } + if ($this->isColumnModified(AppNotesPeer::APP_UID)) { $criteria->add(AppNotesPeer::APP_UID, $this->app_uid); } @@ -997,6 +1053,8 @@ abstract class BaseAppNotes extends BaseObject implements Persistent public function copyInto($copyObj, $deepCopy = false) { + $copyObj->setNoteId($this->note_id); + $copyObj->setAppUid($this->app_uid); $copyObj->setUsrUid($this->usr_uid); diff --git a/workflow/engine/classes/model/om/BaseAppNotesPeer.php b/workflow/engine/classes/model/om/BaseAppNotesPeer.php index 2efabc48c..858e32b61 100644 --- a/workflow/engine/classes/model/om/BaseAppNotesPeer.php +++ b/workflow/engine/classes/model/om/BaseAppNotesPeer.php @@ -25,12 +25,15 @@ abstract class BaseAppNotesPeer const CLASS_DEFAULT = 'classes.model.AppNotes'; /** The total number of columns. */ - const NUM_COLUMNS = 10; + const NUM_COLUMNS = 11; /** The number of lazy-loaded columns. */ const NUM_LAZY_LOAD_COLUMNS = 0; + /** the column name for the NOTE_ID field */ + const NOTE_ID = 'APP_NOTES.NOTE_ID'; + /** the column name for the APP_UID field */ const APP_UID = 'APP_NOTES.APP_UID'; @@ -72,10 +75,10 @@ abstract class BaseAppNotesPeer * e.g. self::$fieldNames[self::TYPE_PHPNAME][0] = 'Id' */ private static $fieldNames = array ( - BasePeer::TYPE_PHPNAME => array ('AppUid', 'UsrUid', 'NoteDate', 'NoteContent', 'NoteType', 'NoteAvailability', 'NoteOriginObj', 'NoteAffectedObj1', 'NoteAffectedObj2', 'NoteRecipients', ), - BasePeer::TYPE_COLNAME => array (AppNotesPeer::APP_UID, AppNotesPeer::USR_UID, AppNotesPeer::NOTE_DATE, AppNotesPeer::NOTE_CONTENT, AppNotesPeer::NOTE_TYPE, AppNotesPeer::NOTE_AVAILABILITY, AppNotesPeer::NOTE_ORIGIN_OBJ, AppNotesPeer::NOTE_AFFECTED_OBJ1, AppNotesPeer::NOTE_AFFECTED_OBJ2, AppNotesPeer::NOTE_RECIPIENTS, ), - BasePeer::TYPE_FIELDNAME => array ('APP_UID', 'USR_UID', 'NOTE_DATE', 'NOTE_CONTENT', 'NOTE_TYPE', 'NOTE_AVAILABILITY', 'NOTE_ORIGIN_OBJ', 'NOTE_AFFECTED_OBJ1', 'NOTE_AFFECTED_OBJ2', 'NOTE_RECIPIENTS', ), - BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ) + BasePeer::TYPE_PHPNAME => array ('NoteId', 'AppUid', 'UsrUid', 'NoteDate', 'NoteContent', 'NoteType', 'NoteAvailability', 'NoteOriginObj', 'NoteAffectedObj1', 'NoteAffectedObj2', 'NoteRecipients', ), + BasePeer::TYPE_COLNAME => array (AppNotesPeer::NOTE_ID, AppNotesPeer::APP_UID, AppNotesPeer::USR_UID, AppNotesPeer::NOTE_DATE, AppNotesPeer::NOTE_CONTENT, AppNotesPeer::NOTE_TYPE, AppNotesPeer::NOTE_AVAILABILITY, AppNotesPeer::NOTE_ORIGIN_OBJ, AppNotesPeer::NOTE_AFFECTED_OBJ1, AppNotesPeer::NOTE_AFFECTED_OBJ2, AppNotesPeer::NOTE_RECIPIENTS, ), + BasePeer::TYPE_FIELDNAME => array ('NOTE_ID', 'APP_UID', 'USR_UID', 'NOTE_DATE', 'NOTE_CONTENT', 'NOTE_TYPE', 'NOTE_AVAILABILITY', 'NOTE_ORIGIN_OBJ', 'NOTE_AFFECTED_OBJ1', 'NOTE_AFFECTED_OBJ2', 'NOTE_RECIPIENTS', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ) ); /** @@ -85,10 +88,10 @@ abstract class BaseAppNotesPeer * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 */ private static $fieldKeys = array ( - BasePeer::TYPE_PHPNAME => array ('AppUid' => 0, 'UsrUid' => 1, 'NoteDate' => 2, 'NoteContent' => 3, 'NoteType' => 4, 'NoteAvailability' => 5, 'NoteOriginObj' => 6, 'NoteAffectedObj1' => 7, 'NoteAffectedObj2' => 8, 'NoteRecipients' => 9, ), - BasePeer::TYPE_COLNAME => array (AppNotesPeer::APP_UID => 0, AppNotesPeer::USR_UID => 1, AppNotesPeer::NOTE_DATE => 2, AppNotesPeer::NOTE_CONTENT => 3, AppNotesPeer::NOTE_TYPE => 4, AppNotesPeer::NOTE_AVAILABILITY => 5, AppNotesPeer::NOTE_ORIGIN_OBJ => 6, AppNotesPeer::NOTE_AFFECTED_OBJ1 => 7, AppNotesPeer::NOTE_AFFECTED_OBJ2 => 8, AppNotesPeer::NOTE_RECIPIENTS => 9, ), - BasePeer::TYPE_FIELDNAME => array ('APP_UID' => 0, 'USR_UID' => 1, 'NOTE_DATE' => 2, 'NOTE_CONTENT' => 3, 'NOTE_TYPE' => 4, 'NOTE_AVAILABILITY' => 5, 'NOTE_ORIGIN_OBJ' => 6, 'NOTE_AFFECTED_OBJ1' => 7, 'NOTE_AFFECTED_OBJ2' => 8, 'NOTE_RECIPIENTS' => 9, ), - BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ) + BasePeer::TYPE_PHPNAME => array ('NoteId' => 0, 'AppUid' => 1, 'UsrUid' => 2, 'NoteDate' => 3, 'NoteContent' => 4, 'NoteType' => 5, 'NoteAvailability' => 6, 'NoteOriginObj' => 7, 'NoteAffectedObj1' => 8, 'NoteAffectedObj2' => 9, 'NoteRecipients' => 10, ), + BasePeer::TYPE_COLNAME => array (AppNotesPeer::NOTE_ID => 0, AppNotesPeer::APP_UID => 1, AppNotesPeer::USR_UID => 2, AppNotesPeer::NOTE_DATE => 3, AppNotesPeer::NOTE_CONTENT => 4, AppNotesPeer::NOTE_TYPE => 5, AppNotesPeer::NOTE_AVAILABILITY => 6, AppNotesPeer::NOTE_ORIGIN_OBJ => 7, AppNotesPeer::NOTE_AFFECTED_OBJ1 => 8, AppNotesPeer::NOTE_AFFECTED_OBJ2 => 9, AppNotesPeer::NOTE_RECIPIENTS => 10, ), + BasePeer::TYPE_FIELDNAME => array ('NOTE_ID' => 0, 'APP_UID' => 1, 'USR_UID' => 2, 'NOTE_DATE' => 3, 'NOTE_CONTENT' => 4, 'NOTE_TYPE' => 5, 'NOTE_AVAILABILITY' => 6, 'NOTE_ORIGIN_OBJ' => 7, 'NOTE_AFFECTED_OBJ1' => 8, 'NOTE_AFFECTED_OBJ2' => 9, 'NOTE_RECIPIENTS' => 10, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ) ); /** @@ -189,6 +192,8 @@ abstract class BaseAppNotesPeer public static function addSelectColumns(Criteria $criteria) { + $criteria->addSelectColumn(AppNotesPeer::NOTE_ID); + $criteria->addSelectColumn(AppNotesPeer::APP_UID); $criteria->addSelectColumn(AppNotesPeer::USR_UID); diff --git a/workflow/engine/config/schema.xml b/workflow/engine/config/schema.xml index 24e4d7490..313c2ed73 100644 --- a/workflow/engine/config/schema.xml +++ b/workflow/engine/config/schema.xml @@ -230,6 +230,7 @@ + @@ -284,7 +285,7 @@ - + @@ -3287,7 +3288,7 @@ - +
@@ -3308,6 +3309,7 @@ + @@ -3318,6 +3320,9 @@ + + + diff --git a/workflow/engine/data/mysql/schema.sql b/workflow/engine/data/mysql/schema.sql index 4946fb777..67608ac2e 100644 --- a/workflow/engine/data/mysql/schema.sql +++ b/workflow/engine/data/mysql/schema.sql @@ -120,6 +120,7 @@ CREATE TABLE `APP_DOCUMENT` `APP_UID` VARCHAR(32) default '' NOT NULL, `DEL_INDEX` INTEGER default 0 NOT NULL, `DOC_UID` VARCHAR(32) default '' NOT NULL, + `DOC_ID` INTEGER default 0, `USR_UID` VARCHAR(32) default '' NOT NULL, `APP_DOC_TYPE` VARCHAR(32) default '' NOT NULL, `APP_DOC_CREATE_DATE` DATETIME NOT NULL, @@ -1583,6 +1584,7 @@ DROP TABLE IF EXISTS `APP_NOTES`; CREATE TABLE `APP_NOTES` ( + `NOTE_ID` INTEGER NOT NULL AUTO_INCREMENT, `APP_UID` VARCHAR(32) default '' NOT NULL, `USR_UID` VARCHAR(32) default '' NOT NULL, `NOTE_DATE` DATETIME NOT NULL, @@ -1593,6 +1595,7 @@ CREATE TABLE `APP_NOTES` `NOTE_AFFECTED_OBJ1` VARCHAR(32) default '', `NOTE_AFFECTED_OBJ2` VARCHAR(32) default '' NOT NULL, `NOTE_RECIPIENTS` MEDIUMTEXT, + UNIQUE KEY `NOTE_ID` (`NOTE_ID`), KEY `indexAppNotesDate`(`APP_UID`, `NOTE_DATE`), KEY `indexAppNotesUser`(`APP_UID`, `USR_UID`) )ENGINE=InnoDB DEFAULT CHARSET='utf8' COMMENT='Application Notes'; diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php index c8e078d8a..15727a9c9 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php @@ -41,7 +41,9 @@ use ProcessMaker\BusinessModel\User as BmUser; use ProcessMaker\Core\System; use ProcessMaker\Exception\UploadException; use ProcessMaker\Model\Application as ModelApplication; +use ProcessMaker\Model\AppNotes; use ProcessMaker\Model\Delegation; +use ProcessMaker\Model\Documents; use ProcessMaker\Plugins\PluginRegistry; use ProcessMaker\Services\OAuth2\Server; use ProcessMaker\Util\DateTime as UtilDateTime; @@ -3843,6 +3845,138 @@ class Cases return $response; } + /** + * Add a case note + * + * @param string $appUid + * @param string $userUid + * @param string $note + * @param bool $sendMail + * @param array $files + * + * @return array + * @throws Exception + */ + public function addNote($appUid, $userUid, $note, $sendMail = false, $files = []) + { + // Register the note + $attributes = [ + "APP_UID" => $appUid, + "USR_UID" => $userUid, + "NOTE_DATE" => date("Y-m-d H:i:s"), + "NOTE_CONTENT" => $note, + "NOTE_TYPE" => "USER", + "NOTE_AVAILABILITY" => "PUBLIC", + "NOTE_RECIPIENTS" => "" + ]; + $response = AppNotes::create($attributes); + // Get the FK + $noteId = $response->NOTE_ID; + + // Register the files related to the note + $this->uploadFilesInCaseNotes($userUid, $appUid, $files, $noteId); + + // Send the email + if ($sendMail) { + // @todo refactor this section the files attached need to send in the email + $case = new ClassesCases(); + $p = $case->getUsersParticipatedInCase($appUid, 'ACTIVE'); + $noteRecipientsList = []; + + foreach ($p["array"] as $key => $userParticipated) { + if ($key != '') { + $noteRecipientsList[] = $key; + } + } + + $noteRecipients = implode(",", $noteRecipientsList); + $note = stripslashes($note); + + $note = new \AppNotes(); + $note->sendNoteNotification($appUid, $userUid, $note, $noteRecipients); + } + } + + /** + * Upload file related to the case notes + * + * @param string $userUid + * @param string $appUid + * @param array $filesReferences + * @param string $appDocUid + * + * @return array + * @throws Exception + */ + public function uploadFilesInCaseNotes($userUid, $appUid, $filesReferences = [], $noteId = 0) + { + if (!empty($_FILES["form"]["name"])) { + $upload = true; + // Array from post upload + foreach ($_FILES["form"]["name"] as $fileIndex => $fileName) { + if (!is_array($fileName)) { + $files[] = [ + 'name' => $_FILES["form"]["name"][$fileIndex], + 'tmp_name' => $_FILES["form"]["tmp_name"][$fileIndex], + 'error' => $_FILES["form"]["error"][$fileIndex] + ]; + } + } + } elseif (!empty($filesReferences)) { + $upload = false; + // Array with path references + foreach ($filesReferences as $fileIndex => $fileName) { + $nameFile = !is_numeric($fileIndex) ? basename($fileIndex) : basename($fileName); + $files[] = [ + 'name' => $nameFile, + 'tmp_name' => $fileName, + 'error' => UPLOAD_ERR_OK + ]; + } + } + + // Get the delIndex related to the case + $cases = new ClassesCases(); + $delIndex = $cases->getCurrentDelegation($appUid); + + // We will to register the files in the database + $response = []; + if (!empty($files)) { + $i = 0; + foreach ($files as $fileIndex => $fileName) { + // There is no error, the file uploaded with success + if ($fileName["error"] === UPLOAD_ERR_OK) { + $appDocUid = G::generateUniqueID(); + $attributes = [ + "DOC_ID" => $noteId, + "APP_DOC_UID" => $appDocUid, + "DOC_VERSION" => 1, + "APP_UID" => $appUid, + "DEL_INDEX" => $delIndex, + "USR_UID" => $userUid, + "DOC_UID" => -1, + "APP_DOC_TYPE" => 'CASE_NOTE', + "APP_DOC_CREATE_DATE" => date("Y-m-d H:i:s"), + "APP_DOC_FILENAME" => $fileName["name"] + ]; + Documents::create($attributes); + + // Upload or move the file + $isUploaded = saveAppDocument($fileName, $appUid, $appDocUid, 1, $upload); + + // List of files uploaded or copy + $response[$i++] = $attributes; + } else { + throw new UploadException($fileName['error']); + } + } + } else { + throw new Exception(G::LoadTranslation('ID_ERROR_UPLOAD_FILE_CONTACT_ADMINISTRATOR')); + } + + return $response; + } + /** * Run the validations related to an Input Document * diff --git a/workflow/engine/src/ProcessMaker/Model/AppNotes.php b/workflow/engine/src/ProcessMaker/Model/AppNotes.php index 2db007ad5..402662991 100644 --- a/workflow/engine/src/ProcessMaker/Model/AppNotes.php +++ b/workflow/engine/src/ProcessMaker/Model/AppNotes.php @@ -6,6 +6,42 @@ use Illuminate\Database\Eloquent\Model; class AppNotes extends Model { + // Set our table name protected $table = 'APP_NOTES'; + // No timestamps public $timestamps = false; + // Primary key + protected $primaryKey = 'NOTE_ID'; + // The IDs are auto-incrementing + public $incrementing = true; + + /** + * The model's default values for attributes. + * + * @var array + */ + protected $attributes = [ + 'NOTE_TYPE' => 'USER', + 'NOTE_ORIGIN_OBJ' => '', + 'NOTE_AFFECTED_OBJ1' => '', + 'NOTE_AFFECTED_OBJ2' => '' + ]; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'APP_UID', + 'USR_UID', + 'NOTE_DATE', + 'NOTE_CONTENT', + 'NOTE_TYPE', + 'NOTE_AVAILABILITY', + 'NOTE_ORIGIN_OBJ', + 'NOTE_AFFECTED_OBJ1', + 'NOTE_AFFECTED_OBJ2', + 'NOTE_RECIPIENTS' + ]; } diff --git a/workflow/engine/src/ProcessMaker/Model/Documents.php b/workflow/engine/src/ProcessMaker/Model/Documents.php new file mode 100644 index 000000000..740e7d689 --- /dev/null +++ b/workflow/engine/src/ProcessMaker/Model/Documents.php @@ -0,0 +1,96 @@ + '', + 'APP_DOC_COMMENT' => '', + 'DOC_UID' => '-1', + 'FOLDER_UID' => '', + 'APP_DOC_PLUGIN' => '', + 'APP_DOC_TAGS' => '', + 'APP_DOC_FIELDNAME' => '', + 'APP_DOC_DRIVE_DOWNLOAD' => 'a:0:{}', + 'SYNC_WITH_DRIVE' => 'UNSYNCHRONIZED', + 'SYNC_PERMISSIONS' => '', + 'APP_DOC_STATUS_DATE' => '', + ]; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'DOC_ID', + 'APP_DOC_UID', + 'DOC_VERSION', + 'APP_DOC_FILENAME', + 'APP_UID', + 'DEL_INDEX', + 'DOC_UID', + 'USR_UID', + 'APP_DOC_TYPE', + 'APP_DOC_CREATE_DATE', + 'APP_DOC_INDEX', + 'FOLDER_UID', + 'APP_DOC_STATUS', + ]; + + /** + * Scope a query to filter an specific case + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $proUid + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeAppUid($query, string $appUid) + { + return $query->where('APP_UID', $appUid); + } + + /** + * Return the documents related to the case + * + * @param int $proId + * @param string $type + * + * @return array + */ + public static function getAppFiles(string $appUid, $type = 'CASE_NOTES') + { + $query = Documents::query()->select(); + $query->appUid($appUid); + $query->where('APP_DOC_TYPE', $type); + $results = $query->get(); + $documentList = []; + $results->each(function ($item, $key) use (&$documentList) { + $documentList[] = $item->toArray(); + }); + + return $documentList; + } +} diff --git a/workflow/engine/src/ProcessMaker/Util/helpers.php b/workflow/engine/src/ProcessMaker/Util/helpers.php index 22272a78e..a844b2eaa 100644 --- a/workflow/engine/src/ProcessMaker/Util/helpers.php +++ b/workflow/engine/src/ProcessMaker/Util/helpers.php @@ -601,3 +601,39 @@ function getMysqlVersion() return $mysqlVersion; } + +/** + * Move the uploaded file to the documents folder + * + * @param array $file + * @param string $appUid + * @param string $appDocUid + * @param int $version + * + * @return string + */ +function saveAppDocument($file, $appUid, $appDocUid, $version = 1, $upload = true) +{ + try { + $info = pathinfo($file["name"]); + $extension = ((isset($info["extension"])) ? $info["extension"] : ""); + //$pathCase = G::getPathFromUID($appUid); + $fileName = $appDocUid . "_" . $version . "." . $extension; + + $pathCase = PATH_DATA_SITE . 'files' . PATH_SEP . G::getPathFromUID($appUid) . PATH_SEP; + + $response = false; + if ($upload) { + $response = G::uploadFile( + $file["tmp_name"], + $pathCase, + $fileName + ); + } else { + G::verifyPath($pathCase, true); + $response = copy($file["tmp_name"], $pathCase . $fileName); + } + } catch (Exception $e) { + throw $e; + } +} From 45ba40b5a1d1a613fff8f8e0ea1da687055e9c06 Mon Sep 17 00:00:00 2001 From: Roly Rudy Gutierrez Pinto Date: Thu, 11 Jun 2020 10:44:47 -0400 Subject: [PATCH 11/24] PMCORE-1527 PMCORE-1421 - Regenerate PM Report Tables Asynchronously --- app/Jobs/GenerateReportTable.php | 14 + tests/resources/structureReportTable.json | 33 ++ .../engine/classes/ReportTablesTest.php | 285 ++++++++++++------ .../classes/model/AdditionalTablesTest.php | 83 +++++ .../src/ProcessMaker/Core/JobsManagerTest.php | 3 +- .../bin/tasks/cliGenerateDataReport.php | 74 ----- .../engine/bin/tasks/cliPopulateTable.php | 21 -- workflow/engine/classes/ReportTables.php | 31 +- workflow/engine/classes/WsBase.php | 3 +- .../engine/classes/model/AdditionalTables.php | 44 ++- workflow/engine/config/databases.php | 61 ++-- .../translations/english/processmaker.en.po | 6 + workflow/engine/controllers/pmTablesProxy.php | 2 +- workflow/engine/data/mysql/insert.sql | 1 + .../Commands/GenerateDataReport.php | 80 ----- .../Commands/PopulateTableReport.php | 42 --- .../src/ProcessMaker/Core/JobsManager.php | 3 +- .../src/ProcessMaker/Core/MultiProcOpen.php | 92 ------ .../engine/src/ProcessMaker/Core/ProcOpen.php | 126 -------- .../engine/src/ProcessMaker/Core/System.php | 3 +- 20 files changed, 401 insertions(+), 606 deletions(-) create mode 100644 app/Jobs/GenerateReportTable.php create mode 100644 tests/resources/structureReportTable.json delete mode 100644 workflow/engine/bin/tasks/cliGenerateDataReport.php delete mode 100644 workflow/engine/bin/tasks/cliPopulateTable.php delete mode 100644 workflow/engine/src/ProcessMaker/Commands/GenerateDataReport.php delete mode 100644 workflow/engine/src/ProcessMaker/Commands/PopulateTableReport.php delete mode 100644 workflow/engine/src/ProcessMaker/Core/MultiProcOpen.php delete mode 100644 workflow/engine/src/ProcessMaker/Core/ProcOpen.php diff --git a/app/Jobs/GenerateReportTable.php b/app/Jobs/GenerateReportTable.php new file mode 100644 index 000000000..480a882d5 --- /dev/null +++ b/app/Jobs/GenerateReportTable.php @@ -0,0 +1,14 @@ +markTestIncomplete("" - . "This test has started using the ./processmaker command, this " - . "command requires the file 'paths_installed.php', that is, a " - . "valid installation of processmaker."); $_SERVER["REQUEST_URI"] = ""; + config(['queue.default' => 'sync']); config(["system.workspace" => "test"]); $workspace = config("system.workspace"); $this->createDBFile($workspace); + $this->createConstantsOfConnection(); } /** @@ -39,6 +38,33 @@ class ReportTablesTest extends TestCase parent::tearDown(); } + /** + * Create constants of connection to databases. + */ + private function createConstantsOfConnection() + { + $constants = [ + 'DB_ADAPTER' => env('mysql'), + 'DB_HOST' => env('DB_HOST'), + 'DB_NAME' => env('DB_DATABASE'), + 'DB_USER' => env('DB_USERNAME'), + 'DB_PASS' => env('DB_PASSWORD'), + 'DB_RBAC_HOST' => env('DB_HOST'), + 'DB_RBAC_NAME' => env('DB_DATABASE'), + 'DB_RBAC_USER' => env('DB_USERNAME'), + 'DB_RBAC_PASS' => env('DB_PASSWORD'), + 'DB_REPORT_HOST' => env('DB_HOST'), + 'DB_REPORT_NAME' => env('DB_DATABASE'), + 'DB_REPORT_USER' => env('DB_USERNAME'), + 'DB_REPORT_PASS' => env('DB_PASSWORD'), + ]; + foreach ($constants as $key => $value) { + if (!defined($key)) { + define($key, $value); + } + } + } + /** * Check if the "populateTable" function returns an array value if entered all parameters. * @test @@ -565,97 +591,6 @@ class ReportTablesTest extends TestCase $this->assertEquals($expected, $actual); } - /** - * Get mapping fields supported by report table. - * @return array - */ - private function getMapFields() - { - return [ - [ - 'sFieldName' => 'var_Text1', - 'sType' => 'char' - ], - [ - 'sFieldName' => 'var_Textarea1', - 'sType' => 'text' - ], - [ - 'sFieldName' => 'var_Dropdown1', - 'sType' => 'char' - ], - [ - 'sFieldName' => 'var_Suggest1', - 'sType' => 'char' - ], - [ - 'sFieldName' => 'var_DateTime1', - 'sType' => 'date' - ], - [ - 'sFieldName' => 'var_String1', - 'sType' => 'char' - ], - [ - 'sFieldName' => 'var_Integer1', - 'sType' => 'number' - ], - [ - 'sFieldName' => 'var_Boolean1', - 'sType' => 'boolean' - ], - [ - 'sFieldName' => 'var_Array1', - 'sType' => 'array' - ] - ]; - } - - /** - * Create fields data by type supported. - * @param array $types - * @return array - */ - private function createFieldsByType($types = []) - { - $fields = []; - $mapping = []; - $faker = Faker\Factory::create(); - $date = $faker->dateTime(); - $mapFields = $this->getMapFields(); - foreach ($mapFields as $key => $value) { - if (!in_array($value['sType'], $types)) { - continue; - } - switch ($value['sType']) { - case 'number': - $mapping[] = $value; - $fields[$value['sFieldName']] = (string) random_int(0, 100); - break; - case 'char': - $mapping[] = $value; - $fields[$value['sFieldName']] = G::generateUniqueID(); - break; - case 'text': - $mapping[] = $value; - $fields[$value['sFieldName']] = G::generateUniqueID(); - break; - case 'date': - $mapping[] = $value; - $fields[$value['sFieldName']] = $date->format('Y-m-d H:i:s'); - break; - case 'boolean': - $mapping[] = $value; - $fields[$value['sFieldName']] = ['0' => 0]; - break; - } - } - return [ - 'data' => $fields, - 'mapping' => $mapping - ]; - } - /** * Prepare data initial for test, the grid parameter is optional if you want * to create a grid type field. @@ -665,7 +600,7 @@ class ReportTablesTest extends TestCase * @param boolean $grid * @return object */ - private function prepareData($tableName, $grid = null) + private function prepareData($tableName, $grid = null, $structure = []) { $applicationNumber = Application::max('APP_NUMBER'); if (is_null($applicationNumber)) { @@ -681,7 +616,10 @@ class ReportTablesTest extends TestCase $taskUid = G::generateUniqueID(); $applicationUid = G::generateUniqueID(); - $structure = $this->createFieldsByType(['number', 'char', 'text', 'date']); + if (empty($structure)) { + $structure = $this->getDataFromFile('structureReportTable.json'); + } + $fields = $structure['mapping']; $dataFields = $structure['data']; $appData = [ @@ -753,4 +691,157 @@ class ReportTablesTest extends TestCase $result->application = $application; return $result; } + + /** + * Check if the "populateTable" method is it filling with missing values into app_data. + * @test + * @covers ReportTables::populateTable + */ + public function it_should_populating_data_with_fields_missing_in_to_app_data() + { + $tableName = 'TestReportTable'; + $result = $this->prepareData($tableName); + $connectionShortName = 'wf'; + $type = 'NORMAL'; + $fields = $result->fields; + $proUid = $result->processUid; + $grid = ''; + + $app = Application::where('APP_UID', '=', $result->applicationUid)->get()->first(); + $appData = unserialize($app->APP_DATA); + unset($appData['var_Textarea1']); + $appData = serialize($appData); + Application::where('APP_UID', '=', $result->applicationUid)->update(['APP_DATA' => $appData]); + + $reportTables = new ReportTables(); + $reportTables->populateTable($tableName, $connectionShortName, $type, $fields, $proUid, $grid); + + $expected = $result->dataFields; + $expected['APP_UID'] = $result->applicationUid; + $expected['APP_NUMBER'] = $result->applicationNumber; + $expected['var_Textarea1'] = ''; + + $actual = (array) DB::table($tableName) + ->select() + ->first(); + + $this->assertEquals($expected, $actual); + } + + /** + * Check if the "populateTable" method is it filling with arrays values. + * @test + * @covers ReportTables::populateTable + */ + public function it_should_populating_data_with_arrays_values() + { + $tableName = 'TestReportTable'; + $result = $this->prepareData($tableName); + $connectionShortName = 'wf'; + $type = 'NORMAL'; + $fields = $result->fields; + $proUid = $result->processUid; + $grid = ''; + + $app = Application::where('APP_UID', '=', $result->applicationUid)->get()->first(); + $appData = unserialize($app->APP_DATA); + $appData['var_Textarea1'] = []; + $appData = serialize($appData); + Application::where('APP_UID', '=', $result->applicationUid)->update(['APP_DATA' => $appData]); + + $reportTables = new ReportTables(); + $reportTables->populateTable($tableName, $connectionShortName, $type, $fields, $proUid, $grid); + + $expected = $result->dataFields; + $expected['APP_UID'] = $result->applicationUid; + $expected['APP_NUMBER'] = $result->applicationNumber; + $expected['var_Textarea1'] = ''; + + $actual = (array) DB::table($tableName) + ->select() + ->first(); + + $this->assertEquals($expected, $actual); + } + + /** + * Check if the "populateTable" method is it filling with missing values into app_data for grids control. + * parameters and type and grid are correct values. + * @test + * @covers ReportTables::populateTable + */ + public function it_should_populating_data_with_all_parameters_with_type_is_grid_fields_missing_in_to_app_data() + { + $tableName = 'TestReportTable'; + $result = $this->prepareData($tableName, true); + $connectionShortName = 'wf'; + $type = 'GRID'; + $fields = $result->fields; + $proUid = $result->processUid; + $grid = 'var_Grid1'; + + $app = Application::where('APP_UID', '=', $result->applicationUid)->get()->first(); + $appData = unserialize($app->APP_DATA); + unset($appData['var_Grid1'][1]['var_Textarea1']); + $appData = serialize($appData); + Application::where('APP_UID', '=', $result->applicationUid)->update(['APP_DATA' => $appData]); + + $reportTables = new ReportTables(); + $reportTables->populateTable($tableName, $connectionShortName, $type, $fields, $proUid, $grid); + + $indexRow = 1; + $expected = $result->appData[$grid]; + foreach ($expected as &$row) { + $row['APP_UID'] = $result->applicationUid; + $row['APP_NUMBER'] = $result->applicationNumber; + $row['ROW'] = (string) ($indexRow++); + } + $expected = array_values($expected); + $expected[0]['var_Textarea1'] = ''; + + $actual = DB::table($tableName) + ->select() + ->get(); + $actual->transform(function ($item, $key) { + return (array) $item; + }); + $actual = $actual->toArray(); + + $this->assertEquals($expected, $actual); + } + + /** + * Check an exception if the input parameters are wrong. + * @test + * @covers ReportTables::populateTable + */ + public function it_should_catch_an_exception() + { + $tableName = 'TestReportTable'; + $result = $this->prepareData($tableName, true); + $connectionShortName = 'wf'; + $type = 'GRID'; + $fields = $result->fields; + $proUid = $result->processUid; + $grid = 'var_Grid1'; + + //assert exception + $this->expectException(Exception::class); + + $reportTables = new ReportTables(); + $reportTables->populateTable($tableName, $connectionShortName, $type, null, $proUid, $grid); + } + + /** + * This gets data from a json file. + * @param string $pathData + * @return array + */ + private function getDataFromFile(string $pathData): array + { + $pathData = PATH_TRUNK . "tests/resources/{$pathData}"; + $data = file_get_contents($pathData); + $result = json_decode($data, JSON_OBJECT_AS_ARRAY); + return $result; + } } diff --git a/tests/unit/workflow/engine/classes/model/AdditionalTablesTest.php b/tests/unit/workflow/engine/classes/model/AdditionalTablesTest.php index be2e7bd61..d2369fb43 100644 --- a/tests/unit/workflow/engine/classes/model/AdditionalTablesTest.php +++ b/tests/unit/workflow/engine/classes/model/AdditionalTablesTest.php @@ -3,17 +3,32 @@ namespace Tests\unit\workflow\engine\classes\model; use AdditionalTables; +use App\Jobs\GenerateReportTable; use Exception; use G; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Schema; use ProcessMaker\BusinessModel\ReportTable; use ProcessMaker\Model\AdditionalTables as AdditionalTablesModel; +use ProcessMaker\Model\Application; +use ProcessMaker\Model\DbSource; +use ProcessMaker\Model\Delegation; +use ProcessMaker\Model\Process; +use ProcessMaker\Model\Task; use Tests\TestCase; class AdditionalTablesTest extends TestCase { + /** + * Set up method. + */ + public function setUp() + { + parent::setUp(); + } + /** * This tests the creation of a PMTable. * @test @@ -198,6 +213,74 @@ class AdditionalTablesTest extends TestCase $this->assertContains($actual[0], $expected, false); } + /** + * Check if populate report table is added to job queue. + * @test + * @covers \AdditionalTables::populateReportTable + */ + public function it_should_test_populate_report_table() + { + $proUid = factory(Process::class)->create()->PRO_UID; + + $task = factory(Task::class)->create([ + 'PRO_UID' => $proUid + ]); + + //local connections + $dbSource = factory(DbSource::class)->create([ + 'PRO_UID' => $proUid, + 'DBS_SERVER' => env('DB_HOST'), + 'DBS_DATABASE_NAME' => env('DB_DATABASE'), + 'DBS_USERNAME' => env('DB_USERNAME'), + 'DBS_PASSWORD' => G::encrypt(env('DB_PASSWORD'), env('DB_DATABASE')) . "_2NnV3ujj3w", + 'DBS_PORT' => '3306', + 'DBS_CONNECTION_TYPE' => 'NORMAL' + ]); + $additionalTable = factory(AdditionalTablesModel::class)->create([ + 'PRO_UID' => $proUid, + 'DBS_UID' => $dbSource->DBS_UID, + ]); + $tableName = $additionalTable->ADD_TAB_NAME; + $name = $additionalTable->ADD_TAB_CLASS_NAME; + $this->createSchema($dbSource->DBS_DATABASE_NAME, $tableName, $name, $dbSource->DBS_UID); + + //external connection + $dbSource = factory(DbSource::class)->create([ + 'PRO_UID' => $proUid, + 'DBS_SERVER' => config('database.connections.testexternal.host'), + 'DBS_DATABASE_NAME' => config('database.connections.testexternal.database'), + 'DBS_USERNAME' => config('database.connections.testexternal.username'), + 'DBS_PASSWORD' => G::encrypt(config('database.connections.testexternal.password'), config('database.connections.testexternal.database')) . "_2NnV3ujj3w", + 'DBS_PORT' => '3306', + 'DBS_CONNECTION_TYPE' => 'NORMAL' + ]); + $additionalTable = factory(AdditionalTablesModel::class)->create([ + 'PRO_UID' => $proUid, + 'DBS_UID' => $dbSource->DBS_UID, + ]); + $tableNameExternal = $additionalTable->ADD_TAB_NAME; + $nameExternal = $additionalTable->ADD_TAB_CLASS_NAME; + $this->createSchema($dbSource->DBS_DATABASE_NAME, $tableNameExternal, $nameExternal, $dbSource->DBS_UID); + + $application = factory(Application::class)->create([ + 'PRO_UID' => $proUid + ]); + factory(Delegation::class)->create([ + 'DEL_THREAD_STATUS' => 'CLOSED', + 'APP_NUMBER' => $application->APP_NUMBER, + 'TAS_UID' => $task->TAS_UID, + ]); + + //assertions + Queue::fake(); + Queue::assertNothingPushed(); + + $additionalTables = new AdditionalTables(); + $additionalTables->populateReportTable($tableName, 'workflow', 'NORMAL', $proUid, '', $additionalTable->ADD_TAB_UID); + + Queue::assertPushed(GenerateReportTable::class); + } + /** * This gets the content from template file. * @param string $pathData diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Core/JobsManagerTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Core/JobsManagerTest.php index 423c722c7..fe08f9d8d 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Core/JobsManagerTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Core/JobsManagerTest.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Core; +use App\Jobs\Email; use Tests\TestCase; class JobsManagerTest extends TestCase @@ -116,7 +117,7 @@ class JobsManagerTest extends TestCase $callback = function() { }; - $actual = $this->object->dispatch('Email', $callback); + $actual = $this->object->dispatch(Email::class, $callback); $this->assertInstanceOf(\Illuminate\Foundation\Bus\PendingDispatch::class, $actual); } diff --git a/workflow/engine/bin/tasks/cliGenerateDataReport.php b/workflow/engine/bin/tasks/cliGenerateDataReport.php deleted file mode 100644 index 0dcdea02f..000000000 --- a/workflow/engine/bin/tasks/cliGenerateDataReport.php +++ /dev/null @@ -1,74 +0,0 @@ - "", - "type=" => "", - "process=" => "", - "gridKey=" => "", - "additionalTable=" => "", - "className=" => "", - "pathWorkspace=" => "", - "start=" => "", - "limit=" => "" - ]; - foreach ($parameters as $key => $value) { - for ($i = 1; $i < count($options); $i++) { - if (strpos($options[$i], $key) !== false) { - $parameters[$key] = str_replace($key, "", $options[$i]); - break; - } - } - } - - //validations - $needed = [ - "process=" - ]; - foreach ($needed as $value) { - if (empty($parameters[$value])) { - CLI::logging("Missing options {$value}.\n"); - return; - } - } - - //run method - $workspaceTools = new WorkspaceTools($workspace); - $workspaceTools->generateDataReport( - $parameters["tableName="], - $parameters["type="], - $parameters["process="], - $parameters["gridKey="], - $parameters["additionalTable="], - $parameters["className="], - $parameters["pathWorkspace="], - (int) $parameters["start="], - (int) $parameters["limit="] - ); -} diff --git a/workflow/engine/bin/tasks/cliPopulateTable.php b/workflow/engine/bin/tasks/cliPopulateTable.php deleted file mode 100644 index c99584c00..000000000 --- a/workflow/engine/bin/tasks/cliPopulateTable.php +++ /dev/null @@ -1,21 +0,0 @@ -populateTableReport($query, $isRbac); -} diff --git a/workflow/engine/classes/ReportTables.php b/workflow/engine/classes/ReportTables.php index 73cba7357..06e430390 100644 --- a/workflow/engine/classes/ReportTables.php +++ b/workflow/engine/classes/ReportTables.php @@ -1,8 +1,8 @@ sPrefix . $tableName; //we have to do the propel connection $database = $this->chooseDB($connectionShortName); @@ -222,7 +225,7 @@ class ReportTables $applications = Application::getByProUid($proUid); $i = 1; $queryValues = ""; - $numberRecords = 1000; + $numberRecords = $reportTableBatchRegeneration; $n = count($applications); foreach ($applications as $application) { $appData = $case->unserializeData($application->APP_DATA); @@ -262,11 +265,12 @@ class ReportTables $queryValues = rtrim($queryValues, ","); $query = $headQuery . $queryValues; $queryValues = ""; - $workspace = config("system.workspace"); - $processesManager = new MultiProcOpen(); - $processesManager->chunk(1, 1, function($size, $start, $limit) use ($query, $workspace) { - return new PopulateTableReport($workspace, $query); - }); + + //add to queue + $closure = function() use($query) { + DB::insert($query); + }; + JobsManager::getSingleton()->dispatch(GenerateReportTable::class, $closure); } } else { if (isset($appData[$grid])) { @@ -304,11 +308,12 @@ class ReportTables $queryValues = rtrim($queryValues, ","); $query = $headQuery . $queryValues; $queryValues = ""; - $workspace = config("system.workspace"); - $processesManager = new MultiProcOpen(); - $processesManager->chunk(1, 1, function($size, $start, $limit) use ($query, $workspace) { - return new PopulateTableReport($workspace, $query); - }); + + //add to queue + $closure = function() use($query) { + DB::insert($query); + }; + JobsManager::getSingleton()->dispatch(GenerateReportTable::class, $closure); } } } diff --git a/workflow/engine/classes/WsBase.php b/workflow/engine/classes/WsBase.php index fff4ae0c2..7e3653b33 100644 --- a/workflow/engine/classes/WsBase.php +++ b/workflow/engine/classes/WsBase.php @@ -1,5 +1,6 @@ dispatch('EmailEvent', $closure); + JobsManager::getSingleton()->dispatch(EmailEvent::class, $closure); $result = new WsResponse(0, G::loadTranslation('ID_MESSAGE_SENT') . ": " . $to); break; default : diff --git a/workflow/engine/classes/model/AdditionalTables.php b/workflow/engine/classes/model/AdditionalTables.php index 71d729b6d..5c4dcba74 100644 --- a/workflow/engine/classes/model/AdditionalTables.php +++ b/workflow/engine/classes/model/AdditionalTables.php @@ -1,8 +1,9 @@ chunk($n, 1000, function($size, $start, $limit) use( - $workspace, - $tableName, - $type, - $processUid, - $gridKey, - $addTabUid, - $className, - $pathWorkspace) { - return new GenerateDataReport( - $workspace, - $tableName, - $type, - $processUid, - $gridKey, - $addTabUid, - $className, - $pathWorkspace, - $start, - $limit); - }); + + //batch process + $config = System::getSystemConfiguration(); + $reportTableBatchRegeneration = $config['report_table_batch_regeneration']; + + $size = $n; + $start = 0; + $limit = $reportTableBatchRegeneration; + + for ($i = 1; $start < $size; $i++) { + $closure = function() use($workspace, $tableName, $type, $processUid, $gridKey, $addTabUid, $className, $pathWorkspace, $start, $limit) { + $workspaceTools = new WorkspaceTools($workspace); + $workspaceTools->generateDataReport($tableName, $type, $processUid, $gridKey, $addTabUid, $className, $pathWorkspace, $start, $limit); + }; + JobsManager::getSingleton()->dispatch(GenerateReportTable::class, $closure); + $start = $i * $limit; + } } /** diff --git a/workflow/engine/config/databases.php b/workflow/engine/config/databases.php index 5cc8432b0..fc3583505 100644 --- a/workflow/engine/config/databases.php +++ b/workflow/engine/config/databases.php @@ -1,27 +1,4 @@ . - * - * For more information, contact Colosa Inc, 2566 Le Jeune Rd., - * Coral Gables, FL, 33134, USA, or email info@colosa.com. - * - */ if (defined('PATH_DB') && !empty(config("system.workspace"))) { @@ -29,7 +6,32 @@ if (defined('PATH_DB') && !empty(config("system.workspace"))) { throw new Exception("Could not find db.php in current workspace " . config("system.workspace")); } - require_once(PATH_DB . config("system.workspace") . '/db.php'); + //These constants must not exist, they will be created by "db.php". + $constants = [ + 'DB_ADAPTER', + 'DB_HOST', + 'DB_NAME', + 'DB_USER', + 'DB_PASS', + 'DB_RBAC_HOST', + 'DB_RBAC_NAME', + 'DB_RBAC_USER', + 'DB_RBAC_PASS' , + 'DB_REPORT_HOST', + 'DB_REPORT_NAME', + 'DB_REPORT_USER', + 'DB_REPORT_PASS', + ]; + $load = true; + foreach ($constants as $value) { + if (defined($value)) { + $load = false; + break; + } + } + if ($load === true) { + require_once(PATH_DB . config("system.workspace") . '/db.php'); + } //to do: enable for other databases $dbType = DB_ADAPTER; $dsn = DB_ADAPTER . '://' . DB_USER . ':' . urlencode(DB_PASS) . '@' . DB_HOST . '/' . DB_NAME; @@ -42,15 +44,12 @@ if (defined('PATH_DB') && !empty(config("system.workspace"))) { switch (DB_ADAPTER) { case 'mysql': - $dsn .= '?encoding=utf8'; - $dsnRbac .= '?encoding=utf8'; + $dsn .= '?encoding=utf8'; + $dsnRbac .= '?encoding=utf8'; $dsnReport .= '?encoding=utf8'; break; case 'mssql': case 'sqlsrv': - //$dsn .= '?sendStringAsUnicode=false'; - //$dsnRbac .= '?sendStringAsUnicode=false'; - //$dsnReport .= '?sendStringAsUnicode=false'; break; default: break; @@ -64,7 +63,7 @@ if (defined('PATH_DB') && !empty(config("system.workspace"))) { $pro ['datasources']['rp']['connection'] = $dsnReport; $pro ['datasources']['rp']['adapter'] = DB_ADAPTER; - + $dbHost = explode(':', DB_HOST); config(['database.connections.workflow.host' => $dbHost[0]]); config(['database.connections.workflow.database' => DB_NAME]); @@ -76,6 +75,6 @@ if (defined('PATH_DB') && !empty(config("system.workspace"))) { } $pro ['datasources']['dbarray']['connection'] = 'dbarray://user:pass@localhost/pm_os'; -$pro ['datasources']['dbarray']['adapter'] = 'dbarray'; +$pro ['datasources']['dbarray']['adapter'] = 'dbarray'; return $pro; diff --git a/workflow/engine/content/translations/english/processmaker.en.po b/workflow/engine/content/translations/english/processmaker.en.po index bcdea0a8f..3a3995fb8 100644 --- a/workflow/engine/content/translations/english/processmaker.en.po +++ b/workflow/engine/content/translations/english/processmaker.en.po @@ -25325,6 +25325,12 @@ msgstr "The PHP files execution was disabled please contact the system administr msgid "Please complete the reassign reason." msgstr "Please complete the reassign reason." +# TRANSLATION +# LABEL/ID_THE_REPORT_TABLE_IS_REGENERATING_PLEASE_COME_BACK_IN_A_FEW_MINUTES +#: LABEL/ID_THE_REPORT_TABLE_IS_REGENERATING_PLEASE_COME_BACK_IN_A_FEW_MINUTES +msgid "The report table is regenerating please come back in a few minutes." +msgstr "The report table is regenerating please come back in a few minutes." + # TRANSLATION # LABEL/ID_THE_UPLOAD_OF_PHP_FILES_WAS_DISABLED #: LABEL/ID_THE_UPLOAD_OF_PHP_FILES_WAS_DISABLED diff --git a/workflow/engine/controllers/pmTablesProxy.php b/workflow/engine/controllers/pmTablesProxy.php index d9d3e660d..d0b992fe2 100644 --- a/workflow/engine/controllers/pmTablesProxy.php +++ b/workflow/engine/controllers/pmTablesProxy.php @@ -1215,7 +1215,7 @@ class pmTablesProxy extends HttpProxyController if (!empty($table) && $table['PRO_UID'] != '') { try { $additionalTables->populateReportTable($table['ADD_TAB_NAME'], PmTable::resolveDbSource($table['DBS_UID']), $table['ADD_TAB_TYPE'], $table['PRO_UID'], $table['ADD_TAB_GRID'], $table['ADD_TAB_UID']); - $result->message = 'Generated for table ' . $table['ADD_TAB_NAME']; + $result->message = G::LoadTranslation("ID_THE_REPORT_TABLE_IS_REGENERATING_PLEASE_COME_BACK_IN_A_FEW_MINUTES"); } catch (Exception $e) { $context = Bootstrap::getDefaultContextLog(); $context['proUid'] = $table['PRO_UID']; diff --git a/workflow/engine/data/mysql/insert.sql b/workflow/engine/data/mysql/insert.sql index df1b025bb..3564d6207 100644 --- a/workflow/engine/data/mysql/insert.sql +++ b/workflow/engine/data/mysql/insert.sql @@ -61112,6 +61112,7 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_THE_NAME_CHANGE_MAY_CAUSE_DATA_LOSS','en','The change might cause data loss in the PM table. Do you want to continue?','2017-03-30') , ( 'LABEL','ID_THE_PHP_FILES_EXECUTION_WAS_DISABLED','en','The PHP files execution was disabled please contact the system administrator.','2018-04-20') , ( 'LABEL','ID_THE_REASON_REASSIGN_USER_EMPTY','en','Please complete the reassign reason.','2016-10-20') , +( 'LABEL','ID_THE_REPORT_TABLE_IS_REGENERATING_PLEASE_COME_BACK_IN_A_FEW_MINUTES','en','The report table is regenerating please come back in a few minutes.','2020-06-01') , ( 'LABEL','ID_THE_UPLOAD_OF_PHP_FILES_WAS_DISABLED','en','The upload of PHP files was disabled please contact the system administrator.','2018-04-20') , ( 'LABEL','ID_THE_USERNAME_EMAIL_IS_INCORRECT','en','The username or email is incorrect','2018-01-18') , ( 'LABEL','ID_THIS_MONTH','en','This Month','2014-01-15') , diff --git a/workflow/engine/src/ProcessMaker/Commands/GenerateDataReport.php b/workflow/engine/src/ProcessMaker/Commands/GenerateDataReport.php deleted file mode 100644 index b14301794..000000000 --- a/workflow/engine/src/ProcessMaker/Commands/GenerateDataReport.php +++ /dev/null @@ -1,80 +0,0 @@ -workspace = $workspace; - $this->tableName = $tableName; - $this->type = $type; - $this->processUid = $processUid; - $this->gridKey = $gridKey; - $this->addTabUid = $addTabUid; - $this->className = $className; - $this->pathWorkspace = $pathWorkspace; - $this->start = $start; - $this->limit = $limit; - $this->setCwd(PATH_TRUNK); - parent::__construct($this->buildCommand()); - } - - /** - * Returns the command to execute. - * @return string - */ - private function buildCommand(): string - { - $command = PHP_BINDIR . "/php " - . "./processmaker " - . "'generate-data-report' " - . "'{$this->workspace}' " - . "'tableName={$this->tableName}' " - . "'type={$this->type}' " - . "'process={$this->processUid}' " - . "'gridKey={$this->gridKey}' " - . "'additionalTable={$this->addTabUid}' " - . "'className={$this->className}' " - . "'pathWorkspace={$this->pathWorkspace}' " - . "'start={$this->start}' " - . "'limit={$this->limit}' "; - return $command; - } -} diff --git a/workflow/engine/src/ProcessMaker/Commands/PopulateTableReport.php b/workflow/engine/src/ProcessMaker/Commands/PopulateTableReport.php deleted file mode 100644 index 310d2e948..000000000 --- a/workflow/engine/src/ProcessMaker/Commands/PopulateTableReport.php +++ /dev/null @@ -1,42 +0,0 @@ -workspace = $workspace; - $this->sql = $sql; - $this->isRbac = $isRbac; - $this->setCwd(PATH_TRUNK); - parent::__construct($this->buildCommand()); - } - - /** - * Returns the command to execute. - * @return string - */ - public function buildCommand() - { - $command = PHP_BINDIR . "/php " - . "./processmaker " - . "'populate-table' " - . "'{$this->workspace}' " - . base64_encode($this->sql) . " " - . ($this->isRbac ? "'1'" : "'0'"); - return $command; - } -} diff --git a/workflow/engine/src/ProcessMaker/Core/JobsManager.php b/workflow/engine/src/ProcessMaker/Core/JobsManager.php index e1df5de01..cda12a92c 100644 --- a/workflow/engine/src/ProcessMaker/Core/JobsManager.php +++ b/workflow/engine/src/ProcessMaker/Core/JobsManager.php @@ -5,7 +5,6 @@ namespace ProcessMaker\Core; use Bootstrap; use Exception; use Illuminate\Support\Facades\Log; -use ProcessMaker\BusinessModel\Factories\Jobs; use ProcessMaker\Core\System; use Propel; @@ -187,7 +186,7 @@ class JobsManager { $environment = $this->getDataSnapshot(); - $instance = Jobs::create($name, function() use ($callback, $environment) { + $instance = $name::dispatch(function() use ($callback, $environment) { try { $this->recoverDataSnapshot($environment); $callback($environment); diff --git a/workflow/engine/src/ProcessMaker/Core/MultiProcOpen.php b/workflow/engine/src/ProcessMaker/Core/MultiProcOpen.php deleted file mode 100644 index 6962f35c7..000000000 --- a/workflow/engine/src/ProcessMaker/Core/MultiProcOpen.php +++ /dev/null @@ -1,92 +0,0 @@ -run($queries); - } - - /** - * Open a set of background processes. - * The array must contain one or more instances of the object inherited from - * the class "ProcessMaker\Core\ProcOpen" - * Returns an array containing the status, content, and errors generated by - * the open process. - * @param array $processes - * @return array - */ - public function run(array $processes): array - { - foreach ($processes as $procOpen) { - $procOpen->open(); - } - return $this->processMonitoring($processes); - } - - /** - * It monitors the open processes, verifying if they have ended or thrown an - * error and later closing the resources related to the process. - * Returns an array containing the status, content, and errors generated by - * the open process. - * @param array $processes - * @return array - */ - private function processMonitoring(array $processes): array - { - sleep($this->sleepTime); //this sleep is very important - $i = 0; - $n = count($processes); - if ($n === 0) { - return []; - } - $outputs = []; - do { - $index = $i % $n; - if (isset($processes[$index])) { - $procOpen = $processes[$index]; - $status = $procOpen->getStatus(); - $contents = $procOpen->getContents(); - $errors = $procOpen->getErrors(); - if ($status->running === false || !empty($errors)) { - $outputs[] = [ - "status" => $status, - "contents" => $contents, - "errors" => $errors, - ]; - $procOpen->terminate(); - $procOpen->close(); - unset($processes[$index]); - } - } - $i = $i + 1; - } while (!empty($processes)); - return $outputs; - } -} diff --git a/workflow/engine/src/ProcessMaker/Core/ProcOpen.php b/workflow/engine/src/ProcessMaker/Core/ProcOpen.php deleted file mode 100644 index 9840db670..000000000 --- a/workflow/engine/src/ProcessMaker/Core/ProcOpen.php +++ /dev/null @@ -1,126 +0,0 @@ -descriptorspec = [ - ['pipe', 'r'], - ['pipe', 'w'], - ['pipe', 'w'] - ]; - $this->command = $command; - } - - /** - * Gets the resource that represents the process. - * @return resource - */ - public function getResource() - { - return $this->resource; - } - - /** - * Sets the process execution directory. - * @param string $cwd - */ - public function setCwd(string $cwd) - { - $this->cwd = $cwd; - } - - /** - * Open a background process. - */ - public function open() - { - if (empty($this->cwd)) { - $this->resource = proc_open($this->command, $this->descriptorspec, $this->pipes); - } else { - $this->resource = proc_open($this->command, $this->descriptorspec, $this->pipes, $this->cwd); - } - } - - /** - * Get the content of the process when it is finished. - * @return string - */ - public function getContents() - { - if (is_resource($this->pipes[1])) { - return stream_get_contents($this->pipes[1]); - } - return ""; - } - - /** - * Get the process errors when it is finished. - * @return string - */ - public function getErrors() - { - if (is_resource($this->pipes[2])) { - return stream_get_contents($this->pipes[2]); - } - return ""; - } - - /** - * Close the resources related to the open process. - * return void - */ - public function close() - { - if (is_resource($this->resource)) { - foreach ($this->pipes as $value) { - fclose($value); - } - proc_close($this->resource); - } - } - - /** - * End the process before it ends. - */ - public function terminate() - { - if (is_resource($this->resource)) { - proc_terminate($this->resource); - } - } - - /** - * Gets the status of the process. - * @return object - */ - public function getStatus() - { - $status = [ - "command" => $this->command, - "pid" => null, - "running" => false, - "signaled" => false, - "stopped" => false, - "exitcode" => -1, - "termsig" => 0, - "stopsig" => 0 - ]; - if (is_resource($this->resource)) { - $status = proc_get_status($this->resource); - } - return (object) $status; - } -} diff --git a/workflow/engine/src/ProcessMaker/Core/System.php b/workflow/engine/src/ProcessMaker/Core/System.php index 91ad8ab49..da2062f79 100644 --- a/workflow/engine/src/ProcessMaker/Core/System.php +++ b/workflow/engine/src/ProcessMaker/Core/System.php @@ -78,7 +78,8 @@ class System 'highlight_home_folder_enable' => 0, 'highlight_home_folder_refresh_time' => 10, 'highlight_home_folder_scope' => 'unassigned', // For now only this list is supported - 'disable_advanced_search_case_title_fulltext' => 0 + 'disable_advanced_search_case_title_fulltext' => 0, + 'report_table_batch_regeneration' => 1000 ]; /** From 536c3588dacbcc3d7f5d5c5677f3d016986a6310 Mon Sep 17 00:00:00 2001 From: Roly Rudy Gutierrez Pinto Date: Thu, 11 Jun 2020 11:49:54 -0400 Subject: [PATCH 12/24] PMCORE-1596 The files attached in the cases notes needs to send in the email notification. --- .../engine/classes/model/AppNotesTest.php | 136 +++++++++++++++++- .../src/ProcessMaker/Model/DocumentsTest.php | 20 +++ workflow/engine/classes/model/AppNotes.php | 41 ++++-- .../translations/english/processmaker.en.po | 6 + workflow/engine/data/mysql/insert.sql | 1 + .../src/ProcessMaker/Model/Documents.php | 16 +++ 6 files changed, 204 insertions(+), 16 deletions(-) diff --git a/tests/unit/workflow/engine/classes/model/AppNotesTest.php b/tests/unit/workflow/engine/classes/model/AppNotesTest.php index e6529b4f4..4d8b3feda 100644 --- a/tests/unit/workflow/engine/classes/model/AppNotesTest.php +++ b/tests/unit/workflow/engine/classes/model/AppNotesTest.php @@ -3,10 +3,14 @@ namespace Tests\unit\workflow\engine\classes\model; use AppNotes as ModelAppNotes; -use ProcessMaker\Model\Delegation; -use ProcessMaker\Model\AppMessage; +use Exception; +use Faker\Factory; use ProcessMaker\Model\Application; +use ProcessMaker\Model\AppMessage; use ProcessMaker\Model\AppNotes; +use ProcessMaker\Model\Delegation; +use ProcessMaker\Model\Documents; +use ProcessMaker\Model\EmailServerModel; use ProcessMaker\Model\User; use Tests\TestCase; @@ -17,12 +21,23 @@ use Tests\TestCase; */ class AppNotesTest extends TestCase { + private $faker; + + /** + * Set up method + */ + public function setUp() + { + parent::setUp(); + $this->faker = Factory::create(); + } + /** * It test the cases notes creation * * @test */ - public function it_test_case_notes_creation() + public function it_test_case_notes_creation() { $application = factory(Application::class)->create(); $user = factory(User::class)->create(); @@ -37,7 +52,7 @@ class AppNotesTest extends TestCase $query = AppNotes::query(); $query->select()->where('APP_UID', $application->APP_UID)->where('USR_UID', $user->USR_UID); $result = $query->get()->values()->toArray(); - $this->assertNotEmpty($result); + $this->assertNotEmpty($result); } /** @@ -45,7 +60,7 @@ class AppNotesTest extends TestCase * * @test */ - public function it_test_case_notes_creation_and_send_email_to_user() + public function it_test_case_notes_creation_and_send_email_to_user() { $application = factory(Application::class)->create(); $user = factory(User::class)->create(); @@ -74,7 +89,7 @@ class AppNotesTest extends TestCase * * @test */ - public function it_test_case_notes_creation_and_send_email() + public function it_test_case_notes_creation_and_send_email() { $application = factory(Application::class)->create(); $user = factory(User::class)->create(); @@ -101,4 +116,111 @@ class AppNotesTest extends TestCase $result = $query->get()->values()->toArray(); $this->assertNotEmpty($result); } -} \ No newline at end of file + + /** + * This test verifies the sending of the notification note with Exception. + * @test + * @covers \AppNotes::sendNoteNotification + */ + public function it_should_test_send_note_notification_with_exception() + { + //assert + $this->expectException(Exception::class); + + $appNotes = new ModelAppNotes(); + $appNotes->sendNoteNotification(null, null, null, null, null, null); + } + + /** + * This test verifies the sending of the notification note. + * @test + * @covers \AppNotes::sendNoteNotification + */ + public function it_should_test_send_note_notification_without_user() + { + $user = User::where('USR_UID', '=', '00000000000000000000000000000001') + ->get() + ->first(); + $application = factory(Application::class)->create(); + $delegation = factory(Delegation::class)->create([ + 'APP_UID' => $application->APP_UID, + 'USR_UID' => $user->USR_UID + ]); + + $params = [ + $application->APP_UID, + '', + '', + $user->USR_UID, + $this->faker->email, + $delegation->DEL_INDEX + ]; + $appNotes = new ModelAppNotes(); + $appNotes->sendNoteNotification(...$params); + + //assert + $appMessage = AppMessage::where('APP_UID', '=', $application->APP_UID)->get()->first()->toArray(); + $this->assertArrayHasKey('APP_UID', $appMessage); + $this->assertEquals($appMessage['APP_UID'], $application->APP_UID); + } + + /** + * This test verifies the sending of the notification note with attach files. + * @test + * @covers \AppNotes::sendNoteNotification + */ + public function it_should_test_send_note_notification_with_attach_files() + { + $user = User::where('USR_UID', '=', '00000000000000000000000000000001') + ->get() + ->first(); + $application = factory(Application::class)->create(); + $delegation = factory(Delegation::class)->create([ + 'APP_UID' => $application->APP_UID, + 'USR_UID' => $user->USR_UID + ]); + $appNote = factory(AppNotes::class)->create(); + $appDocument = factory(Documents::class)->create([ + 'APP_UID' => $application->APP_UID, + 'DOC_ID' => $appNote->NOTE_ID + ]); + factory(EmailServerModel::class)->create([ + 'MESS_DEFAULT' => 1 + ]); + + $params = [ + $application->APP_UID, + $user->USR_UID, + '', + $user->USR_UID, + $this->faker->email, + $delegation->DEL_INDEX + ]; + $appNotes = new ModelAppNotes(); + $appNotes->sendNoteNotification(...$params); + + //assert + $appMessage = AppMessage::where('APP_UID', '=', $application->APP_UID)->get()->first()->toArray(); + $this->assertArrayHasKey('APP_UID', $appMessage); + $this->assertEquals($appMessage['APP_UID'], $application->APP_UID); + } + + /** + * This test verify if exists attachment files. + * @test + * @covers \AppNotes::getAttachedFilesFromTheCaseNote + */ + public function it_should_test_get_attached_files_from_the_casenote() + { + $appNote = factory(AppNotes::class)->create(); + $appDocument = factory(Documents::class)->create([ + 'DOC_ID' => $appNote->NOTE_ID + ]); + + $appUid = $appDocument->APP_UID; + $appNotes = new ModelAppNotes(); + $result = $appNotes->getAttachedFilesFromTheCaseNote($appUid); + + $this->assertNotEmpty($result); + } +} diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Model/DocumentsTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Model/DocumentsTest.php index bc882b79d..b1fe83257 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Model/DocumentsTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Model/DocumentsTest.php @@ -1,7 +1,9 @@ getAppFiles($appDoc->APP_UID, Documents::DOC_TYPE_CASE_NOTE); $this->assertNotEmpty($res); } + + /** + * This test verify if exists attachment files. + * @test + * @covers Documents::getAttachedFilesFromTheCaseNote + */ + public function it_should_test_get_attached_files_from_the_casenote() + { + $appNote = factory(AppNotes::class)->create(); + $appDocument = factory(Documents::class)->create([ + 'DOC_ID' => $appNote->NOTE_ID + ]); + + $appUid = $appDocument->APP_UID; + $result = Documents::getAttachedFilesFromTheCaseNote($appUid); + + $this->assertNotEmpty($result); + } } \ No newline at end of file diff --git a/workflow/engine/classes/model/AppNotes.php b/workflow/engine/classes/model/AppNotes.php index 6e99dc408..17c885359 100644 --- a/workflow/engine/classes/model/AppNotes.php +++ b/workflow/engine/classes/model/AppNotes.php @@ -1,6 +1,7 @@ addCaseNote() * @see AppNotes->postNewNote() * @see workflow/engine/src/ProcessMaker/Util/helpers.php::postNote() - */ - public function sendNoteNotification ($appUid, $usrUid, $noteContent, $noteRecipients, $from = '', $delIndex = 0) + */ + public function sendNoteNotification($appUid, $usrUid, $noteContent, $noteRecipients, $from = '', $delIndex = 0) { try { - $configuration = System::getEmailConfiguration(); $msgError = ""; - if (! isset( $configuration['MESS_ENABLED'] ) || $configuration['MESS_ENABLED'] != '1') { + if (!isset($configuration['MESS_ENABLED']) || $configuration['MESS_ENABLED'] != '1') { $msgError = "The default configuration wasn't defined"; $configuration['MESS_ENGINE'] = ''; } @@ -211,20 +211,27 @@ class AppNotes extends BaseAppNotes $cases = new Cases(); $fieldCase = $cases->loadCase($appUid, $delIndex); $configNoteNotification['subject'] = G::LoadTranslation('ID_MESSAGE_SUBJECT_NOTE_NOTIFICATION') . " @#APP_TITLE "; + //Define the body for the notification $configNoteNotification['body'] = $this->getBodyCaseNote($authorName, $noteContent); $body = nl2br(G::replaceDataField($configNoteNotification['body'], $fieldCase, 'mysql', false)); + $attachFileLinks = $this->getAttachedFilesFromTheCaseNote($appUid); + if (!empty($attachFileLinks)) { + $body = $body . "
" . G::LoadTranslation('ID_ATTACHED_FILES') . ": " . implode("
", $attachFileLinks) . "."; + } $users = new Users(); $recipientsArray = explode(",", $noteRecipients); foreach ($recipientsArray as $recipientUid) { $userInfo = $users->load($recipientUid); - $to = ((($userInfo['USR_FIRSTNAME'] != '') || ($userInfo['USR_LASTNAME'] != '')) ? $userInfo['USR_FIRSTNAME'] . ' ' . $userInfo['USR_LASTNAME'] . ' ' : '') . '<' . $userInfo['USR_EMAIL'] . '>'; + $ifUserNameDefined = $userInfo['USR_FIRSTNAME'] != '' || $userInfo['USR_LASTNAME'] != ''; + $to = ($ifUserNameDefined ? $userInfo['USR_FIRSTNAME'] . ' ' . $userInfo['USR_LASTNAME'] . ' ' : '') . '<' . $userInfo['USR_EMAIL'] . '>'; $spool = new SpoolRun(); $spool->setConfig($configuration); - $messageArray = AppMessage::buildMessageRow( + + $parameters = [ '', $appUid, $delIndex, @@ -244,7 +251,8 @@ class AppNotes extends BaseAppNotes (isset($fieldCase['APP_NUMBER'])) ? $fieldCase['APP_NUMBER'] : 0, (isset($fieldCase['PRO_ID'])) ? $fieldCase['PRO_ID'] : 0, (isset($fieldCase['TAS_ID'])) ? $fieldCase['TAS_ID'] : 0 - ); + ]; + $messageArray = AppMessage::buildMessageRow(...$parameters); $spool->create($messageArray); if ($msgError == '') { @@ -252,14 +260,29 @@ class AppNotes extends BaseAppNotes $spool->sendMail(); } } - } - //Send derivation notification - End } catch (Exception $exception) { throw $exception; } } + /** + * Get attached files from the case note, this require appUid. + * @param string $appUid + * @return array + */ + public function getAttachedFilesFromTheCaseNote(string $appUid): array + { + $attachFileLinks = []; + $url = System::getServerMainPath(); + $result = Documents::getAttachedFilesFromTheCaseNote($appUid); + $result->each(function($item) use($url, &$attachFileLinks) { + $href = $url . "/cases/casesShowCaseNotes?a={$item->APP_DOC_UID}=&v={$item->DOC_VERSION}"; + $attachFileLinks[] = "{$item->APP_DOC_FILENAME}"; + }); + return $attachFileLinks; + } + public function addCaseNote($applicationUid, $userUid, $note, $sendMail) { $response = $this->postNewNote($applicationUid, $userUid, $note, false); diff --git a/workflow/engine/content/translations/english/processmaker.en.po b/workflow/engine/content/translations/english/processmaker.en.po index bcdea0a8f..be838ea9b 100644 --- a/workflow/engine/content/translations/english/processmaker.en.po +++ b/workflow/engine/content/translations/english/processmaker.en.po @@ -2575,6 +2575,12 @@ msgstr "Attach" msgid "Attached" msgstr "Attached" +# TRANSLATION +# LABEL/ID_ATTACHED_FILES +#: LABEL/ID_ATTACHED_FILES +msgid "Attached files" +msgstr "Attached files" + # TRANSLATION # LABEL/ID_ATTRIBUTES #: LABEL/ID_ATTRIBUTES diff --git a/workflow/engine/data/mysql/insert.sql b/workflow/engine/data/mysql/insert.sql index df1b025bb..f0e2571f9 100644 --- a/workflow/engine/data/mysql/insert.sql +++ b/workflow/engine/data/mysql/insert.sql @@ -57232,6 +57232,7 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_ASSIGN_VARIABLES_OUT','en','Assign Variables Out','2014-01-15') , ( 'LABEL','ID_ATTACH','en','Attach','2014-01-15') , ( 'LABEL','ID_ATTACHED_DB','en','Attached','2014-10-08') , +( 'LABEL','ID_ATTACHED_FILES','en','Attached files','2020-06-10') , ( 'LABEL','ID_ATTRIBUTES','en','Attributes','2014-01-15') , ( 'LABEL','ID_ATTRIBUTE_HAS_INVALID_ELEMENT_KEY','en','The attribute {0}, has an invalid element (incorrect keys).','2014-05-20') , ( 'LABEL','ID_AT_RISK','en','At Risk','2014-01-15') , diff --git a/workflow/engine/src/ProcessMaker/Model/Documents.php b/workflow/engine/src/ProcessMaker/Model/Documents.php index 740e7d689..85939198d 100644 --- a/workflow/engine/src/ProcessMaker/Model/Documents.php +++ b/workflow/engine/src/ProcessMaker/Model/Documents.php @@ -93,4 +93,20 @@ class Documents extends Model return $documentList; } + + /** + * Get attached files from the case note. + * @param string $appUid + * @return object + */ + public static function getAttachedFilesFromTheCaseNote(string $appUid) + { + $result = Documents::select('APP_DOCUMENT.APP_DOC_UID', 'APP_DOCUMENT.DOC_VERSION', 'APP_DOCUMENT.APP_DOC_FILENAME') + ->join('APP_NOTES', function($join) use($appUid) { + $join->on('APP_NOTES.NOTE_ID', '=', 'APP_DOCUMENT.DOC_ID') + ->where('APP_DOCUMENT.APP_UID', '=', $appUid); + }) + ->get(); + return $result; + } } From b38daebb9410f3d3b5594bac8d03b4c88e54d424 Mon Sep 17 00:00:00 2001 From: Fabio Guachalla Date: Wed, 10 Jun 2020 16:29:46 -0400 Subject: [PATCH 13/24] PMCORE-1584: Case notes must have the option to download the attached files Correction Download Show a lot of files Large Text and Tooltip Correction Styles --- .../skinEngine/base/css/pmos-xtheme-gray.css | 8 ++++++++ .../neoclassic/css/pmos-xtheme-gray.css | 8 ++++++++ .../uxmodern/css/pmos-xtheme-gray.css | 8 ++++++++ .../skinEngine/uxs/css/pmos-xtheme-gray.css | 8 ++++++++ workflow/engine/templates/app/main.js | 18 ++++++++++-------- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/workflow/engine/skinEngine/base/css/pmos-xtheme-gray.css b/workflow/engine/skinEngine/base/css/pmos-xtheme-gray.css index 95e5fa4a5..3e6daffd5 100644 --- a/workflow/engine/skinEngine/base/css/pmos-xtheme-gray.css +++ b/workflow/engine/skinEngine/base/css/pmos-xtheme-gray.css @@ -1675,4 +1675,12 @@ white-space:normal; .navPanelBottom .x-toolbar-cell { clear: both; height: 50px; +} +.nav_list li a { + padding: 2px 2px 2px 10px; + overflow: hidden; + text-overflow: ellipsis; + width: 100px; + white-space: nowrap; + display: inline-block; } \ No newline at end of file diff --git a/workflow/engine/skinEngine/neoclassic/css/pmos-xtheme-gray.css b/workflow/engine/skinEngine/neoclassic/css/pmos-xtheme-gray.css index 13a945ac1..c2432451d 100644 --- a/workflow/engine/skinEngine/neoclassic/css/pmos-xtheme-gray.css +++ b/workflow/engine/skinEngine/neoclassic/css/pmos-xtheme-gray.css @@ -1868,3 +1868,11 @@ white-space:normal; clear: both; height: 50px; } +.nav_list li a { + padding: 2px 2px 2px 10px; + overflow: hidden; + text-overflow: ellipsis; + width: 100px; + white-space: nowrap; + display: inline-block; +} \ No newline at end of file diff --git a/workflow/engine/skinEngine/uxmodern/css/pmos-xtheme-gray.css b/workflow/engine/skinEngine/uxmodern/css/pmos-xtheme-gray.css index 18463561a..b2bb36066 100644 --- a/workflow/engine/skinEngine/uxmodern/css/pmos-xtheme-gray.css +++ b/workflow/engine/skinEngine/uxmodern/css/pmos-xtheme-gray.css @@ -1704,4 +1704,12 @@ text-decoration: none; .navPanelBottom .x-toolbar-cell { clear: both; height: 50px; +} +.nav_list li a { + padding: 2px 2px 2px 10px; + overflow: hidden; + text-overflow: ellipsis; + width: 100px; + white-space: nowrap; + display: inline-block; } \ No newline at end of file diff --git a/workflow/engine/skinEngine/uxs/css/pmos-xtheme-gray.css b/workflow/engine/skinEngine/uxs/css/pmos-xtheme-gray.css index e36622302..85f34c690 100644 --- a/workflow/engine/skinEngine/uxs/css/pmos-xtheme-gray.css +++ b/workflow/engine/skinEngine/uxs/css/pmos-xtheme-gray.css @@ -1249,4 +1249,12 @@ td.x-cnotes-label { .navPanelBottom .x-toolbar-cell { clear: both; height: 50px; +} +.nav_list li a { + padding: 2px 2px 2px 10px; + overflow: hidden; + text-overflow: ellipsis; + width: 100px; + white-space: nowrap; + display: inline-block; } \ No newline at end of file diff --git a/workflow/engine/templates/app/main.js b/workflow/engine/templates/app/main.js index cef2996a1..a54f3998a 100644 --- a/workflow/engine/templates/app/main.js +++ b/workflow/engine/templates/app/main.js @@ -39,7 +39,7 @@ function openCaseNotesWindow(appUid1, delIndex, modalSw, appTitle, proUid, taskU url: "../appProxy/getNotesList?appUid=" + appUid + "&delIndex=" + delIndex + "&pro=" + proUid + "&tas=" + taskUid, root: 'notes', totalProperty: 'totalCount', - fields: ['USR_USERNAME','USR_FIRSTNAME','USR_LASTNAME','USR_FULL_NAME','NOTE_DATE','NOTE_CONTENT', 'USR_UID', 'user'], + fields: ['USR_USERNAME','USR_FIRSTNAME','USR_LASTNAME','USR_FULL_NAME','NOTE_DATE','NOTE_CONTENT', 'USR_UID','USR_EMAIL', 'attachments', 'user'], baseParams:{ start:0, limit:startRecord+loadSize @@ -112,12 +112,13 @@ function openCaseNotesWindow(appUid1, delIndex, modalSw, appTitle, proUid, taskU emptyText: _('ID_CASE_NOTES_EMPTY'), cls: 'x-cnotes-view', tpl: '' + - '
' + + '
' + '' + '' + '' + '' + @@ -129,12 +130,13 @@ function openCaseNotesWindow(appUid1, delIndex, modalSw, appTitle, proUid, taskU singleSelect: true, prepareData: function(data){ - //data.shortName = Ext.util.Format.ellipsis(data.name, 15); - //data.sizeString = Ext.util.Format.fileSize(data.size); - //data.dateString = data.lastmod.format("m/d/Y g:i a"); - - data.user = _FNF(data.USR_USERNAME, data.USR_FIRSTNAME, data.USR_LASTNAME); + var i; + data.user = _FNF(data.USR_EMAIL, data.USR_FIRSTNAME, data.USR_LASTNAME); data.NOTE_CONTENT = data.NOTE_CONTENT.replace(/\n/g,'
'); + data.files = ""; + for (i = 0; i < data.attachments.length; i += 1) { + data.files += "" + data.attachments[i].APP_DOC_FILENAME + ""; + } return data; }, From 5e9ee175d873d7832c86c89a7ca0d1c5adfa124d Mon Sep 17 00:00:00 2001 From: Fabio Guachalla Date: Thu, 11 Jun 2020 16:46:49 -0400 Subject: [PATCH 14/24] PMCORE-1584 --- workflow/engine/templates/app/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workflow/engine/templates/app/main.js b/workflow/engine/templates/app/main.js index a54f3998a..0b9fc62e8 100644 --- a/workflow/engine/templates/app/main.js +++ b/workflow/engine/templates/app/main.js @@ -112,7 +112,7 @@ function openCaseNotesWindow(appUid1, delIndex, modalSw, appTitle, proUid, taskU emptyText: _('ID_CASE_NOTES_EMPTY'), cls: 'x-cnotes-view', tpl: '' + - '
'+ '

{user}

'+ - '

{NOTE_CONTENT}

'+ + '

{NOTE_CONTENT}

'+ + '
' + '

'+_('ID_POSTED_AT')+' {NOTE_DATE}

'+ '
' + + '
' + '' + '' + '
'+ @@ -127,7 +127,7 @@ function openCaseNotesWindow(appUid1, delIndex, modalSw, appTitle, proUid, taskU itemSelector: 'div.x-cnotes-source', overClass: 'x-cnotes-over', selectedClass: 'x-cnotes-selected', - singleSelect: true, + singleSelect: false, prepareData: function(data){ var i; From d629ea9d4a1158a26f3cbdc7cb20af88e2fc979e Mon Sep 17 00:00:00 2001 From: Andrea Adamczyk Date: Wed, 10 Jun 2020 17:27:27 -0400 Subject: [PATCH 15/24] PMCORE-1604-A --- .../methods/cases/casesShowCaseNotes.php | 94 +++++++++++++++++++ workflow/public_html/sysGeneric.php | 1 + 2 files changed, 95 insertions(+) create mode 100644 workflow/engine/methods/cases/casesShowCaseNotes.php diff --git a/workflow/engine/methods/cases/casesShowCaseNotes.php b/workflow/engine/methods/cases/casesShowCaseNotes.php new file mode 100644 index 000000000..22e5df7d5 --- /dev/null +++ b/workflow/engine/methods/cases/casesShowCaseNotes.php @@ -0,0 +1,94 @@ +getLastAppDocVersion($_GET['a']); +} else { + $docVersion = $_GET['v']; +} + +$appDocument->fields = $appDocument->load($_GET['a'], $docVersion); + +//Check if the document is a case note document +if ($appDocument->fields['APP_DOC_TYPE'] != 'CASE_NOTE') { + G::header('Location: /errors/error403.php'); + die(); +} + +//Check if the user can be download the input Document +//Send the parameter v = Version +//Send the parameter a = Case UID +if ($RBAC->userCanAccess('PM_FOLDERS_ALL') != 1 && defined('DISABLE_DOWNLOAD_DOCUMENTS_SESSION_VALIDATION') && DISABLE_DOWNLOAD_DOCUMENTS_SESSION_VALIDATION == 0) { + if (!$appDocument->canDownloadInput($_SESSION['USER_LOGGED'], $_GET['a'], $docVersion)) { + G::header('Location: /errors/error403.php'); + die(); + } +} + +//Create the Cases object +$case = new Cases(); +//Get the case information to get the processUid +$processUid = $case->getCaseInfo($appDocument->fields['APP_UID'], $_SESSION['USER_LOGGED'])->pro_uid; +//Get the user authorization +$userCanAccess = $case->userAuthorization( + $_SESSION['USER_LOGGED'], + $processUid, + $appDocument->fields['APP_UID'], + ['PM_ALLCASES'], + ['CASES_NOTES' => 'VIEW'] +); + +//Check if the user has the Case Notes permissions +if ($userCanAccess['objectPermissions']['CASES_NOTES'] != 1) { + G::header('Location: /errors/error403.php'); + die(); +} + +$appDocUid = $appDocument->getAppDocUid(); +$docVersionInformation = $appDocument->getDocVersion(); +$info = pathinfo($appDocument->getAppDocFilename()); +$ext = (isset($info['extension']) ? $info['extension'] : ''); + +$download = true; + +//Get the document path +$appUid = G::getPathFromUID($appDocument->fields['APP_UID']); +$file = G::getPathFromFileUID($appDocument->fields['APP_UID'], $appDocUid); + +$realPath = PATH_DOCUMENT . $appUid . '/' . $file[0] . $file[1] . '_' . $docVersionInformation . '.' . $ext; +$realPath1 = PATH_DOCUMENT . $appUid . '/' . $file[0] . $file[1] . '.' . $ext; +$sw_file_exists = false; +if (file_exists($realPath)) { + $sw_file_exists = true; +} elseif (file_exists($realPath1)) { + $sw_file_exists = true; + $realPath = $realPath1; +} + +if (!$sw_file_exists) { + $error_message = G::LoadTranslation('ID_ERROR_STREAMING_FILE'); + G::SendMessageText($error_message, "ERROR"); + G::header('Location: ' . $_SERVER['HTTP_REFERER']); + die(); +} else { + $nameFile = $appDocument->fields['APP_DOC_FILENAME']; + G::streamFile($realPath, $download, $nameFile); //download +} diff --git a/workflow/public_html/sysGeneric.php b/workflow/public_html/sysGeneric.php index bb5c9eb63..838b097af 100644 --- a/workflow/public_html/sysGeneric.php +++ b/workflow/public_html/sysGeneric.php @@ -976,6 +976,7 @@ if (!defined('EXECUTE_BY_CRON')) { $noLoginFiles[] = 'cases_SaveData'; $noLoginFiles[] = 'cases_Derivate'; $noLoginFiles[] = 'cases_NextStep'; + $noLoginFiles[] = 'casesShowCaseNotes'; $noLoginFiles[] = 'genericAjax'; $noLoginFiles[] = 'casesSaveDataView'; $noLoginFiles[] = 'propelTableAjax'; From b593e2521b9bbb7de6fc91b3b59c78d88d7038b9 Mon Sep 17 00:00:00 2001 From: Rodrigo Quelca Date: Thu, 11 Jun 2020 16:32:26 -0400 Subject: [PATCH 16/24] PMCORE-1583: Case Notes widget must support attach files in web application fix CR notes fix CR notes 2 after user press addNote button all upload fields will be cleaned improve widget add files validations text area with was updated --- .../translations/english/processmaker.en.po | 12 + workflow/engine/data/mysql/insert.sql | 2 + workflow/engine/templates/app/main.js | 241 ++++++++++++------ 3 files changed, 170 insertions(+), 85 deletions(-) diff --git a/workflow/engine/content/translations/english/processmaker.en.po b/workflow/engine/content/translations/english/processmaker.en.po index be838ea9b..e0d1b1379 100644 --- a/workflow/engine/content/translations/english/processmaker.en.po +++ b/workflow/engine/content/translations/english/processmaker.en.po @@ -1993,6 +1993,12 @@ msgstr "Add Data to PM table" msgid "Add field" msgstr "Add field" +# TRANSLATION +# LABEL/ID_ADD_FILE +#: LABEL/ID_ADD_FILE +msgid "Add file" +msgstr "Add file" + # TRANSLATION # LABEL/ID_ADD_HORIZONTAL_LINE #: LABEL/ID_ADD_HORIZONTAL_LINE @@ -2575,6 +2581,12 @@ msgstr "Attach" msgid "Attached" msgstr "Attached" +# TRANSLATION +# LABEL/ID_ATTACH_FILE +#: LABEL/ID_ATTACH_FILE +msgid "Attach file" +msgstr "Attach file" + # TRANSLATION # LABEL/ID_ATTACHED_FILES #: LABEL/ID_ATTACHED_FILES diff --git a/workflow/engine/data/mysql/insert.sql b/workflow/engine/data/mysql/insert.sql index f0e2571f9..0a07360af 100644 --- a/workflow/engine/data/mysql/insert.sql +++ b/workflow/engine/data/mysql/insert.sql @@ -57133,6 +57133,7 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_ADD_CUSTOM_COLUMN','en','Add Custom Column','2014-01-15') , ( 'LABEL','ID_ADD_DATA_PMTABLE','en','Add Data to PM table','2014-10-10') , ( 'LABEL','ID_ADD_FIELD','en','Add field','2014-01-15') , +( 'LABEL','ID_ADD_FILE','en','Add file','2020-06-11') , ( 'LABEL','ID_ADD_HORIZONTAL_LINE','en','Add horizontal line','2015-02-20') , ( 'LABEL','ID_ADD_LICENSE','en','Please add a new license','2014-01-15') , ( 'LABEL','ID_ADD_MESSAGE','en','Add message','2014-01-15') , @@ -57232,6 +57233,7 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_ASSIGN_VARIABLES_OUT','en','Assign Variables Out','2014-01-15') , ( 'LABEL','ID_ATTACH','en','Attach','2014-01-15') , ( 'LABEL','ID_ATTACHED_DB','en','Attached','2014-10-08') , +( 'LABEL','ID_ATTACH_FILE','en','Attach file','2020-06-11') , ( 'LABEL','ID_ATTACHED_FILES','en','Attached files','2020-06-10') , ( 'LABEL','ID_ATTRIBUTES','en','Attributes','2014-01-15') , ( 'LABEL','ID_ATTRIBUTE_HAS_INVALID_ELEMENT_KEY','en','The attribute {0}, has an invalid element (incorrect keys).','2014-05-20') , diff --git a/workflow/engine/templates/app/main.js b/workflow/engine/templates/app/main.js index 0b9fc62e8..8d9e79aaf 100644 --- a/workflow/engine/templates/app/main.js +++ b/workflow/engine/templates/app/main.js @@ -4,8 +4,9 @@ var storeNotes; var appUid; var title; var summaryWindowOpened = false; - var toolTipChkSendMail; +var caseNotesForm; +var uploadItemsSize = 5; function closeCaseNotesWindow(){ if(Ext.get("caseNotesWindowPanel")){ @@ -173,12 +174,101 @@ function openCaseNotesWindow(appUid1, delIndex, modalSw, appTitle, proUid, taskU } ] }); + /** + * Factory to create upload files field dinamically + * @return {Object} + */ + function uploadFileFactory () { + return { + xtype: 'fileuploadfield', + emptyText: '', + fieldLabel: _('ID_ATTACH_FILE'), + buttonText: _('ID_SELECT_FILE'), + name: 'filesToUpload[]', + allowBlank: true, + width : '70%', + validator: function (filePath) { + var flag = false; + if (caseNotesWindow.isVisible() === false || filePath === "") { + return true; + } + filePath = filePath.replace(/^\s|\s$/g, ""); //trims string + if (filePath.match(/([^\/\\]+)\.(pdf|gif|jpg|png|doc|docx|xls|xlsx|txt|mp4|mpv|mpeg|mpg|mov)$/i)) { + flag = true; + } else { + messageError = _('ID_ERROR_UPLOADING_IMAGE_TYPE'); + PMExt.notify(_('ID_ERROR'), messageError); + flag = false; + this.setRawValue(null); + } + return flag; + } + }; + }; + // Cases notes form to insert coments and attach files + caseNotesForm = new Ext.FormPanel({ + width: 462, + anchor: '100%', + baseCls: 'x-plain', + fileUpload: true, + items: + [ + { + text : _('ID_NEW_NOTE'), + xtype : 'textarea', + id : 'caseNoteText', + name : 'caseNoteText', + width : '98%', + height : 100, + hideLabel: true, + maxLengthText : 1500, + allowBlank :false, + selectOnFocus :true, + enableKeyEvents: true, + listeners : { + scope : this, + keyup : updateTextCtr, + keydown: updateTextCtr, + 'change': function(field, newVal, oldVal) { + var textAreaValue = newVal.replace(/^\s+/,'').replace(/\s+$/,''); + field.setValue(textAreaValue.trim()); + Ext.getCmp('caseNoteText').focus(false, 200); + } + } + } + ], + buttons: + [ + { + text: _('ID_ADD_FILE'), + id: 'btnAddFile', + type: 'button', + handler: function () { + var uploadFields = caseNotesForm.findByType('fileuploadfield'); + if (uploadFields.length >= 1 && uploadFields.length < uploadItemsSize) { + if (uploadFields[uploadFields.length - 1].getValue() !== "") { + caseNotesForm.add(uploadFileFactory()); + caseNotesForm.doLayout(); + caseNotesWindow.doLayout(); + } else { + messageError = _('ID_PLEASE_SELECT_FILES_TO_UPLOAD'); + PMExt.notify(_('ID_ERROR'), messageError); + } + } + if (uploadFields.length === uploadItemsSize - 1) { + this.setDisabled(true); + } + + } + } + ] + }); caseNotesWindow = new Ext.Window({ title: _('ID_CASES_NOTES'), //Title of the Window id: 'caseNotesWindowPanel', //ID of the Window Panel width: 480, //Width of the Window - resizable: true, //Resize of the Window, if false - it cannot be resized + resizable: false, //Resize of the Window, if false - it cannot be resized closable: true, //Hide close button of the Window modal: modalSw, //When modal:true it make the window modal and mask everything behind it when displayed //iconCls: 'ICON_CASES_NOTES', @@ -205,33 +295,7 @@ function openCaseNotesWindow(appUid1, delIndex, modalSw, appTitle, proUid, taskU } } ], - tbar:[ - new Ext.form.TextArea({ - text : _('ID_NEW_NOTE'), - xtype : 'textarea', - id : 'caseNoteText', - name : 'caseNoteText', - width : 440, - grow : true, - height : 100, - growMin: 100, - growMax: 80, - maxLengthText : 1500, - allowBlank :false, - selectOnFocus :true, - enableKeyEvents: true, - listeners : { - scope : this, - keyup : updateTextCtr, - keydown: updateTextCtr, - 'change': function(field, newVal, oldVal){ - var textAreaValue = newVal.replace(/^\s+/,'').replace(/\s+$/,''); - field.setValue(textAreaValue.trim()); - Ext.getCmp('caseNoteText').focus(false, 200); - } - } - }) - ], + tbar:[caseNotesForm], rowtbar: [ [ { @@ -283,6 +347,9 @@ function openCaseNotesWindow(appUid1, delIndex, modalSw, appTitle, proUid, taskU this.loadMask = new Ext.LoadMask(this.body, { msg:_('ID_LOADING') }); + caseNotesForm.add(uploadFileFactory()); + caseNotesForm.doLayout(); + caseNotesWindow.doLayout(); }, close:function(){ if (typeof(parent.setFlag) != 'undefined') { @@ -322,6 +389,7 @@ function updateTextCtr(body, event) { function newNoteHandler() { + var i; newNoteAreaActive = newNoteAreaActive ? false : true; if (newNoteAreaActive) { Ext.getCmp('addCancelBtn').setText(''); @@ -351,6 +419,14 @@ function newNoteHandler() document.getElementById('countChar').style.display = 'block'; Ext.getCmp('caseNoteText').focus(); Ext.getCmp('caseNoteText').reset(); + uploadFields = caseNotesForm.findByType('fileuploadfield'); + // clean the first upload field + uploadFields[0].setRawValue(null); + for (i = 1; i < uploadFields.length; i += 1) { + caseNotesForm.remove(uploadFields[i]); + } + caseNotesForm.doLayout(); + Ext.getCmp('btnAddFile').setDisabled(false); document.getElementById('countChar').innerHTML = '1500'; caseNotesWindow.doLayout(); } @@ -358,81 +434,76 @@ function newNoteHandler() caseNotesWindow.doLayout(); } -function sendNote() -{ +function sendNote(){ var noteText = Ext.getCmp('caseNoteText').getValue(); - if (noteText == "") { return false; } - newNoteHandler(); - Ext.getCmp('caseNoteText').focus(); Ext.getCmp('caseNoteText').reset(); Ext.getCmp('caseNoteText').setDisabled(true); Ext.getCmp('sendBtn').setDisabled(true); Ext.getCmp('addCancelBtn').setDisabled(true); statusBarMessage( _('ID_CASES_NOTE_POSTING'), true); - Ext.Ajax.request({ - url : '../appProxy/postNote' , - params : { - appUid: appUid, - noteText: noteText, - swSendMail: (Ext.getCmp("chkSendMail").checked == true)? 1 : 0 - }, - success: function ( result, request ) { - var data = Ext.util.JSON.decode(result.responseText); - if(data.success=="success"){ - Ext.getCmp('caseNoteText').setDisabled(false); - Ext.getCmp('sendBtn').setDisabled(false); - Ext.getCmp('addCancelBtn').setDisabled(false); - if (data.message != '') { - Ext.Msg.show({ + + caseNotesForm.getForm().submit({ + clientValidation: true, + url: '../appProxy/postNote', + params: { + appUid: appUid, + noteText: noteText, + swSendMail: (Ext.getCmp("chkSendMail").checked === true) ? 1 : 0 + }, + success: function ( result, request ) { + var data = Ext.util.JSON.decode(request.response.responseText); + if(data.success=="success"){ + Ext.getCmp('caseNoteText').setDisabled(false); + Ext.getCmp('sendBtn').setDisabled(false); + Ext.getCmp('addCancelBtn').setDisabled(false); + if (data.message != '') { + Ext.Msg.show({ + title : _('ID_CASES_NOTE_POST_ERROR'), + msg : data.message, + icon : Ext.MessageBox.WARNING, + buttons : Ext.Msg.OK, + fn : function(btn) { + statusBarMessage( _('ID_CASES_NOTE_POST_SUCCESS'), false,true); + storeNotes.load(); + } + }); + } else { + statusBarMessage( _('ID_CASES_NOTE_POST_SUCCESS'), false,true); + storeNotes.load(); + } + } else if (data.lostSession) { + Ext.Msg.show({ title : _('ID_CASES_NOTE_POST_ERROR'), msg : data.message, - icon : Ext.MessageBox.WARNING, + icon : Ext.MessageBox.ERROR, buttons : Ext.Msg.OK, fn : function(btn) { - statusBarMessage( _('ID_CASES_NOTE_POST_SUCCESS'), false,true); - storeNotes.load(); + try { + prnt = parent.parent; + top.location = top.location; + } catch (err) { + parent.location = parent.location; + } } - }); + }); } else { - statusBarMessage( _('ID_CASES_NOTE_POST_SUCCESS'), false,true); - storeNotes.load(); + Ext.getCmp('caseNoteText').setDisabled(false); + Ext.getCmp('sendBtn').setDisabled(false); + Ext.getCmp('addCancelBtn').setDisabled(false); + statusBarMessage( _('ID_CASES_NOTE_POST_ERROR'), false,false); + Ext.MessageBox.alert(_('ID_CASES_NOTE_POST_ERROR'), data.message); + } - } else if (data.lostSession) { - Ext.Msg.show({ - title : _('ID_CASES_NOTE_POST_ERROR'), - msg : data.message, - icon : Ext.MessageBox.ERROR, - buttons : Ext.Msg.OK, - fn : function(btn) { - try - { - prnt = parent.parent; - top.location = top.location; - } - catch (err) - { - parent.location = parent.location; - } - } - }); - } else { - Ext.getCmp('caseNoteText').setDisabled(false); - Ext.getCmp('sendBtn').setDisabled(false); - Ext.getCmp('addCancelBtn').setDisabled(false); - statusBarMessage( _('ID_CASES_NOTE_POST_ERROR'), false,false); - Ext.MessageBox.alert(_('ID_CASES_NOTE_POST_ERROR'), data.message); - + }, + failure: function ( result, request) { + statusBarMessage( _('ID_CASES_NOTE_POST_FAILED'), false,false); + Ext.MessageBox.alert(_('ID_CASES_NOTE_POST_FAILED'), result.responseText); } - }, - failure: function ( result, request) { - statusBarMessage( _('ID_CASES_NOTE_POST_FAILED'), false,false); - Ext.MessageBox.alert(_('ID_CASES_NOTE_POST_FAILED'), result.responseText); - } }); } From 5dbaae523fe09e0b23d9b1c055cfbb9cb8a36251 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Wed, 10 Jun 2020 12:15:01 -0400 Subject: [PATCH 17/24] PMCORE-1585 --- gulliver/system/class.g.php | 2 - .../engine/classes/model/AppNotesTest.php | 2 +- .../ProcessMaker/BusinessModel/CasesTest.php | 3 +- workflow/engine/classes/model/AppNotes.php | 36 ++++--- workflow/engine/controllers/appProxy.php | 45 ++++++--- .../src/ProcessMaker/BusinessModel/Cases.php | 95 +++++++++++++------ .../src/ProcessMaker/Model/AppNotes.php | 59 ++++++++++++ .../src/ProcessMaker/Model/Documents.php | 40 +++++++- .../engine/src/ProcessMaker/Util/helpers.php | 6 +- 9 files changed, 227 insertions(+), 61 deletions(-) diff --git a/gulliver/system/class.g.php b/gulliver/system/class.g.php index 8e9e9af86..ee129521e 100644 --- a/gulliver/system/class.g.php +++ b/gulliver/system/class.g.php @@ -2708,8 +2708,6 @@ class G $nameToSave = $filter->validateInput($nameToSave, "path"); @chmod($path . "/" . $nameToSave, $permission); umask($oldumask); - - return true; } catch (Exception $oException) { throw $oException; } diff --git a/tests/unit/workflow/engine/classes/model/AppNotesTest.php b/tests/unit/workflow/engine/classes/model/AppNotesTest.php index 4d8b3feda..b86714211 100644 --- a/tests/unit/workflow/engine/classes/model/AppNotesTest.php +++ b/tests/unit/workflow/engine/classes/model/AppNotesTest.php @@ -219,7 +219,7 @@ class AppNotesTest extends TestCase $appUid = $appDocument->APP_UID; $appNotes = new ModelAppNotes(); - $result = $appNotes->getAttachedFilesFromTheCaseNote($appUid); + $result = $appNotes->getAttachedFilesFromTheCaseNote($appNote->NOTE_ID); $this->assertNotEmpty($result); } diff --git a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php index 3c7da31c5..aa55a5548 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php @@ -124,7 +124,8 @@ class CasesTest extends TestCase // Upload the file $case = new Cases(); $result = $case->uploadFilesInCaseNotes('00000000000000000000000000000001', $application->APP_UID, $filesReferences); - $result = head($result); + $this->assertNotEmpty($result['attachments']); + $result = head($result['attachments']); $this->assertNotEmpty($result); $this->assertArrayHasKey('APP_UID', $result); $this->assertEquals($application->APP_UID, $result['APP_UID']); diff --git a/workflow/engine/classes/model/AppNotes.php b/workflow/engine/classes/model/AppNotes.php index 17c885359..e659bab4f 100644 --- a/workflow/engine/classes/model/AppNotes.php +++ b/workflow/engine/classes/model/AppNotes.php @@ -181,6 +181,7 @@ class AppNotes extends BaseAppNotes * @param string $noteRecipients * @param string $from * @param integer $delIndex + * @param integer $noteId * @return void * @throws Exception * @@ -188,7 +189,15 @@ class AppNotes extends BaseAppNotes * @see AppNotes->postNewNote() * @see workflow/engine/src/ProcessMaker/Util/helpers.php::postNote() */ - public function sendNoteNotification($appUid, $usrUid, $noteContent, $noteRecipients, $from = '', $delIndex = 0) + public function sendNoteNotification( + $appUid, + $usrUid, + $noteContent, + $noteRecipients, + $from = '', + $delIndex = 0, + $noteId = 0 + ) { try { $configuration = System::getEmailConfiguration(); @@ -216,9 +225,13 @@ class AppNotes extends BaseAppNotes $configNoteNotification['body'] = $this->getBodyCaseNote($authorName, $noteContent); $body = nl2br(G::replaceDataField($configNoteNotification['body'], $fieldCase, 'mysql', false)); - $attachFileLinks = $this->getAttachedFilesFromTheCaseNote($appUid); + // Get the files related to the specific case note + if ($noteId !== 0) { + $attachFileLinks = $this->getAttachedFilesFromTheCaseNote($noteId); + } + if (!empty($attachFileLinks)) { - $body = $body . "
" . G::LoadTranslation('ID_ATTACHED_FILES') . ": " . implode("
", $attachFileLinks) . "."; + $body = $body . "
" . G::LoadTranslation('ID_ATTACHED_FILES') . ": 
" . implode("
", $attachFileLinks); } $users = new Users(); $recipientsArray = explode(",", $noteRecipients); @@ -267,19 +280,20 @@ class AppNotes extends BaseAppNotes } /** - * Get attached files from the case note, this require appUid. - * @param string $appUid + * Get attached files from a specific case note + * @param int $docId * @return array */ - public function getAttachedFilesFromTheCaseNote(string $appUid): array + public function getAttachedFilesFromTheCaseNote(int $docId): array { $attachFileLinks = []; $url = System::getServerMainPath(); - $result = Documents::getAttachedFilesFromTheCaseNote($appUid); - $result->each(function($item) use($url, &$attachFileLinks) { - $href = $url . "/cases/casesShowCaseNotes?a={$item->APP_DOC_UID}=&v={$item->DOC_VERSION}"; - $attachFileLinks[] = "{$item->APP_DOC_FILENAME}"; - }); + $result = Documents::getFiles($docId); + foreach ($result as $item) { + $href = $url . "/cases/casesShowCaseNotes?a={$item['APP_DOC_UID']}=&v={$item['DOC_VERSION']}"; + $attachFileLinks[] = "{$item['APP_DOC_FILENAME']}"; + } + return $attachFileLinks; } diff --git a/workflow/engine/controllers/appProxy.php b/workflow/engine/controllers/appProxy.php index 3d9f519f4..ab30a6028 100644 --- a/workflow/engine/controllers/appProxy.php +++ b/workflow/engine/controllers/appProxy.php @@ -1,4 +1,17 @@ tas; } + + // Get user logged $usrUid = $_SESSION['USER_LOGGED']; + // Review if the user has the permissions $respView = $case->getAllObjectsFrom($proUid, $appUid, $tasUid, $usrUid, "VIEW", $delIndex); $respBlock = $case->getAllObjectsFrom($proUid, $appUid, $tasUid, $usrUid, "BLOCK", $delIndex); - if ($respView['CASES_NOTES'] == 0 && $respBlock['CASES_NOTES'] == 0) { return [ 'totalCount' => 0, @@ -99,27 +114,30 @@ class AppProxy extends HttpProxyController ]; } - $usrUid = isset($_SESSION['USER_LOGGED']) ? $_SESSION['USER_LOGGED'] : ""; - $appNotes = new AppNotes(); - $response = $appNotes->getNotesList($appUid, '', $httpData->start, $httpData->limit); + // Get the notes + $appNote = new Notes(); + $response = $appNote->getNotes($appUid, $httpData->start, $httpData->limit); $response = AppNotes::applyHtmlentitiesInNotes($response); + // Prepare the response + $documents = new Documents(); $iterator = 0; - foreach ($response['array']['notes'] as $value) { - $response ['array']['notes'][$iterator]['NOTE_DATE'] = DateTime::convertUtcToTimeZone($value['NOTE_DATE']); + foreach ($response['notes'] as $value) { + $response['notes'][$iterator]['NOTE_DATE'] = DateTime::convertUtcToTimeZone($value['NOTE_DATE']); + $response['notes'][$iterator]['attachments'] = $documents->getFiles($value['NOTE_ID']); $iterator++; } require_once("classes/model/Application.php"); - $oApplication = new Application(); - $aApplication = $oApplication->Load($appUid); - $response['array']['appTitle'] = $aApplication['APP_TITLE']; + $application = new Application(); + $appInfo = $application->Load($appUid); + $response['appTitle'] = $appInfo['APP_TITLE']; - return $response['array']; + return $response; } /** - * post Note Action + * Post a note * * @param string $httpData->appUid (optional, if it is not passed try use $_SESSION['APPLICATION']) * @return array containg the case notes @@ -143,9 +161,10 @@ class AppProxy extends HttpProxyController $this->setSendResponse(false); //Add note case - $appNote = new AppNotes(); + $cases = new BmCases(); try { - $response = $appNote->addCaseNote($appUid, $usrUid, $noteContent, intval($httpData->swSendMail)); + $sendMail = intval($httpData->swSendMail); + $response = $cases->addNote($appUid, $usrUid, $noteContent, $sendMail); } catch (Exception $error) { $response = new stdclass(); $response->success = 'success'; diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php index 15727a9c9..571ab3b66 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php @@ -14,6 +14,7 @@ use AppHistoryPeer; use Application; use ApplicationPeer; use Applications; +use AppNotes; use AppNotesPeer; use AppSolr; use BasePeer; @@ -41,7 +42,7 @@ use ProcessMaker\BusinessModel\User as BmUser; use ProcessMaker\Core\System; use ProcessMaker\Exception\UploadException; use ProcessMaker\Model\Application as ModelApplication; -use ProcessMaker\Model\AppNotes; +use ProcessMaker\Model\AppNotes as Notes; use ProcessMaker\Model\Delegation; use ProcessMaker\Model\Documents; use ProcessMaker\Plugins\PluginRegistry; @@ -3869,16 +3870,25 @@ class Cases "NOTE_AVAILABILITY" => "PUBLIC", "NOTE_RECIPIENTS" => "" ]; - $response = AppNotes::create($attributes); + $newNote = Notes::create($attributes); // Get the FK - $noteId = $response->NOTE_ID; + $noteId = $newNote->NOTE_ID; + $attachments = []; // Register the files related to the note - $this->uploadFilesInCaseNotes($userUid, $appUid, $files, $noteId); + if (!empty($files) || !empty($_FILES["filesToUpload"])) { + $filesResponse = $this->uploadFilesInCaseNotes($userUid, $appUid, $files, $noteId); + foreach ($filesResponse['attachments'] as $key => $value) { + $attachments[$key] = []; + $attachments[$key]['APP_DOC_FILENAME'] = $value['APP_DOC_FILENAME']; + $attachments[$key]['LINK'] = "../cases/cases_ShowDocument?a=" . $value["APP_DOC_UID"] . "&v=" . $value["DOC_VERSION"]; + } + + } // Send the email if ($sendMail) { - // @todo refactor this section the files attached need to send in the email + // Get the recipients $case = new ClassesCases(); $p = $case->getUsersParticipatedInCase($appUid, 'ACTIVE'); $noteRecipientsList = []; @@ -3892,9 +3902,19 @@ class Cases $noteRecipients = implode(",", $noteRecipientsList); $note = stripslashes($note); - $note = new \AppNotes(); - $note->sendNoteNotification($appUid, $userUid, $note, $noteRecipients); + // Send the notification + $appNote = new AppNotes(); + $appNote->sendNoteNotification($appUid, $userUid, $note, $noteRecipients, '', 0, $noteId); } + + // Prepare the response + $result = []; + $result['success'] = 'success'; + $result['message'] = ''; + $result['attachments'] = $attachments; + $result['attachment_errors'] = []; + + return $result; } /** @@ -3910,15 +3930,20 @@ class Cases */ public function uploadFilesInCaseNotes($userUid, $appUid, $filesReferences = [], $noteId = 0) { - if (!empty($_FILES["form"]["name"])) { + $files = []; + if (!empty($_FILES["filesToUpload"])) { $upload = true; - // Array from post upload - foreach ($_FILES["form"]["name"] as $fileIndex => $fileName) { - if (!is_array($fileName)) { + // This format is from ext-js multipart + $filesName = !empty($_FILES["filesToUpload"]["name"]) ? $_FILES["filesToUpload"]["name"] : []; + $filesTmpName = !empty($_FILES["filesToUpload"]["tmp_name"]) ? $_FILES["filesToUpload"]["tmp_name"] : []; + $filesError = !empty($_FILES["filesToUpload"]["error"]) ? $_FILES["filesToUpload"]["error"] : []; + + foreach ($filesName as $index => $value) { + if (!empty($value)) { $files[] = [ - 'name' => $_FILES["form"]["name"][$fileIndex], - 'tmp_name' => $_FILES["form"]["tmp_name"][$fileIndex], - 'error' => $_FILES["form"]["error"][$fileIndex] + 'name' => $filesName[$index], + 'tmp_name' => $filesTmpName[$index], + 'error' => $filesError[$index] ]; } } @@ -3941,31 +3966,43 @@ class Cases // We will to register the files in the database $response = []; + $response['attachments'] = []; + $response['attachment_errors'] = []; if (!empty($files)) { $i = 0; + $j = 0; foreach ($files as $fileIndex => $fileName) { // There is no error, the file uploaded with success if ($fileName["error"] === UPLOAD_ERR_OK) { $appDocUid = G::generateUniqueID(); - $attributes = [ - "DOC_ID" => $noteId, - "APP_DOC_UID" => $appDocUid, - "DOC_VERSION" => 1, - "APP_UID" => $appUid, - "DEL_INDEX" => $delIndex, - "USR_UID" => $userUid, - "DOC_UID" => -1, - "APP_DOC_TYPE" => 'CASE_NOTE', - "APP_DOC_CREATE_DATE" => date("Y-m-d H:i:s"), - "APP_DOC_FILENAME" => $fileName["name"] - ]; - Documents::create($attributes); // Upload or move the file $isUploaded = saveAppDocument($fileName, $appUid, $appDocUid, 1, $upload); - // List of files uploaded or copy - $response[$i++] = $attributes; + // If the file was uploaded correctly we will to register in the DB + if ($isUploaded) { + $attributes = [ + "DOC_ID" => $noteId, + "APP_DOC_UID" => $appDocUid, + "DOC_VERSION" => 1, + "APP_UID" => $appUid, + "DEL_INDEX" => $delIndex, + "USR_UID" => $userUid, + "DOC_UID" => -1, + "APP_DOC_TYPE" => 'CASE_NOTE', + "APP_DOC_CREATE_DATE" => date("Y-m-d H:i:s"), + "APP_DOC_FILENAME" => $fileName["name"] + ]; + Documents::create($attributes); + + // List of files uploaded or copy + $response['attachments'][$i++] = $attributes; + } else { + $response['attachment_errors'][$j++] = [ + 'error' => 'error', + 'file' => $fileName["name"] + ]; + } } else { throw new UploadException($fileName['error']); } diff --git a/workflow/engine/src/ProcessMaker/Model/AppNotes.php b/workflow/engine/src/ProcessMaker/Model/AppNotes.php index 402662991..66c00b43b 100644 --- a/workflow/engine/src/ProcessMaker/Model/AppNotes.php +++ b/workflow/engine/src/ProcessMaker/Model/AppNotes.php @@ -44,4 +44,63 @@ class AppNotes extends Model 'NOTE_AFFECTED_OBJ2', 'NOTE_RECIPIENTS' ]; + + /** + * Scope a query to filter an specific case + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param string $appUid + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeAppUid($query, string $appUid) + { + return $query->where('APP_UID', $appUid); + } + + /** + * Return the documents related to the case + * + * @param string $appUid + * @param int $start + * @param int $limit + * @param string $dir + * + * @return array + */ + public static function getNotes(string $appUid, $start = 0, $limit = 25, $dir = 'DESC') + { + $query = AppNotes::query()->select([ + 'NOTE_ID', + 'APP_UID', + 'NOTE_DATE', + 'NOTE_CONTENT', + 'NOTE_TYPE', + 'NOTE_AVAILABILITY', + 'USERS.USR_UID', + 'USERS.USR_USERNAME', + 'USERS.USR_FIRSTNAME', + 'USERS.USR_LASTNAME' + ]); + $query->leftJoin('USERS', function ($join) { + $join->on('USERS.USR_UID', '=', 'APP_NOTES.USR_UID'); + }); + $query->appUid($appUid); + $query->orderBy('NOTE_DATE', $dir); + // Add pagination to the query + $query->offset($start)->limit($limit); + + $results = $query->get(); + $notes = []; + $notes['notes'] = []; + $results->each(function ($item, $key) use (&$notes) { + $row = $item->toArray(); + $row['NOTE_CONTENT'] = stripslashes($row['NOTE_CONTENT']); + $notes['notes'][] = $row; + }); + + // Add the total of rows to return + $notes['totalCount'] = $limit; + + return $notes; + } } diff --git a/workflow/engine/src/ProcessMaker/Model/Documents.php b/workflow/engine/src/ProcessMaker/Model/Documents.php index 85939198d..ec9b45d59 100644 --- a/workflow/engine/src/ProcessMaker/Model/Documents.php +++ b/workflow/engine/src/ProcessMaker/Model/Documents.php @@ -64,7 +64,7 @@ class Documents extends Model * Scope a query to filter an specific case * * @param \Illuminate\Database\Eloquent\Builder $query - * @param string $proUid + * @param string $appUid * @return \Illuminate\Database\Eloquent\Builder */ public function scopeAppUid($query, string $appUid) @@ -72,10 +72,22 @@ class Documents extends Model return $query->where('APP_UID', $appUid); } + /** + * Scope a query to filter an specific reference file + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param int $docId + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeDocId($query, int $docId) + { + return $query->where('DOC_ID', $docId); + } + /** * Return the documents related to the case * - * @param int $proId + * @param string $appUid * @param string $type * * @return array @@ -96,7 +108,9 @@ class Documents extends Model /** * Get attached files from the case note. + * * @param string $appUid + * * @return object */ public static function getAttachedFilesFromTheCaseNote(string $appUid) @@ -109,4 +123,26 @@ class Documents extends Model ->get(); return $result; } + + /** + * Return the documents related to the specific DOC_ID + * + * @param int $docId + * + * @return array + */ + public static function getFiles(int $docId) + { + $query = Documents::query()->select(['APP_DOC_UID', 'APP_DOC_FILENAME', 'DOC_VERSION']); + $query->docId($docId); + $results = $query->get(); + $documentList = []; + $results->each(function ($item, $key) use (&$documentList) { + $row = $item->toArray(); + $row['LINK'] = "../cases/cases_ShowDocument?a=" . $row["APP_DOC_UID"] . "&v=" . $row["DOC_VERSION"]; + $documentList[] = $row; + }); + + return $documentList; + } } diff --git a/workflow/engine/src/ProcessMaker/Util/helpers.php b/workflow/engine/src/ProcessMaker/Util/helpers.php index a844b2eaa..e63d20a30 100644 --- a/workflow/engine/src/ProcessMaker/Util/helpers.php +++ b/workflow/engine/src/ProcessMaker/Util/helpers.php @@ -617,22 +617,24 @@ function saveAppDocument($file, $appUid, $appDocUid, $version = 1, $upload = tru try { $info = pathinfo($file["name"]); $extension = ((isset($info["extension"])) ? $info["extension"] : ""); - //$pathCase = G::getPathFromUID($appUid); $fileName = $appDocUid . "_" . $version . "." . $extension; $pathCase = PATH_DATA_SITE . 'files' . PATH_SEP . G::getPathFromUID($appUid) . PATH_SEP; $response = false; if ($upload) { - $response = G::uploadFile( + G::uploadFile( $file["tmp_name"], $pathCase, $fileName ); + $response = true; } else { G::verifyPath($pathCase, true); $response = copy($file["tmp_name"], $pathCase . $fileName); } + + return $response; } catch (Exception $e) { throw $e; } From 008ed8a730fdcd59c762b41c05fdbde380f632c1 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Fri, 12 Jun 2020 15:16:11 -0400 Subject: [PATCH 18/24] PMCORE-1585 --- workflow/engine/classes/model/AppNotes.php | 2 +- workflow/engine/src/ProcessMaker/BusinessModel/Cases.php | 2 +- workflow/engine/src/ProcessMaker/Model/Documents.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/workflow/engine/classes/model/AppNotes.php b/workflow/engine/classes/model/AppNotes.php index e659bab4f..823096a79 100644 --- a/workflow/engine/classes/model/AppNotes.php +++ b/workflow/engine/classes/model/AppNotes.php @@ -290,7 +290,7 @@ class AppNotes extends BaseAppNotes $url = System::getServerMainPath(); $result = Documents::getFiles($docId); foreach ($result as $item) { - $href = $url . "/cases/casesShowCaseNotes?a={$item['APP_DOC_UID']}=&v={$item['DOC_VERSION']}"; + $href = $url . "/cases/casesShowCaseNotes?a={$item['APP_DOC_UID']}&v={$item['DOC_VERSION']}"; $attachFileLinks[] = "{$item['APP_DOC_FILENAME']}"; } diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php index 571ab3b66..ab320ad74 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php @@ -3881,7 +3881,7 @@ class Cases foreach ($filesResponse['attachments'] as $key => $value) { $attachments[$key] = []; $attachments[$key]['APP_DOC_FILENAME'] = $value['APP_DOC_FILENAME']; - $attachments[$key]['LINK'] = "../cases/cases_ShowDocument?a=" . $value["APP_DOC_UID"] . "&v=" . $value["DOC_VERSION"]; + $attachments[$key]['LINK'] = "../cases/casesShowCaseNotes?a=" . $value["APP_DOC_UID"] . "&v=" . $value["DOC_VERSION"]; } } diff --git a/workflow/engine/src/ProcessMaker/Model/Documents.php b/workflow/engine/src/ProcessMaker/Model/Documents.php index ec9b45d59..8a42abde3 100644 --- a/workflow/engine/src/ProcessMaker/Model/Documents.php +++ b/workflow/engine/src/ProcessMaker/Model/Documents.php @@ -139,7 +139,7 @@ class Documents extends Model $documentList = []; $results->each(function ($item, $key) use (&$documentList) { $row = $item->toArray(); - $row['LINK'] = "../cases/cases_ShowDocument?a=" . $row["APP_DOC_UID"] . "&v=" . $row["DOC_VERSION"]; + $row['LINK'] = "../cases/casesShowCaseNotes?a=" . $row["APP_DOC_UID"] . "&v=" . $row["DOC_VERSION"]; $documentList[] = $row; }); From 7e916cebd57e96bb6caa2e29b8e643aca5c58f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20Cesar=20Laura=20Avenda=C3=B1o?= Date: Fri, 12 Jun 2020 22:17:42 +0000 Subject: [PATCH 19/24] PMCORE-1644 --- workflow/engine/classes/class.pmFunctions.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/workflow/engine/classes/class.pmFunctions.php b/workflow/engine/classes/class.pmFunctions.php index 149c9d85b..74570f924 100644 --- a/workflow/engine/classes/class.pmFunctions.php +++ b/workflow/engine/classes/class.pmFunctions.php @@ -249,15 +249,16 @@ function executeQuery ($SqlStatement, $DBConnectionUID = 'workflow', $aParameter if (is_null(config('database.connections.' . $DBConnectionUID . '.driver'))) { // Force to load the external connections DbConnections::loadAdditionalConnections(); - if (config('database.connections.' . $DBConnectionUID . '.driver') !== 'oracle') { - // If the connections drivers are "mysql", "pgsql" or "sqlsrv" we're using Laravel - $con = DB::connection($DBConnectionUID); - $con->beginTransaction(); - } else { - // If the connection driver is "oracle" we're using the native oci8 functions - $con = Propel::getConnection($DBConnectionUID); - $con->begin(); - } + } + + if (config('database.connections.' . $DBConnectionUID . '.driver') !== 'oracle') { + // If the connections drivers are "mysql", "pgsql" or "sqlsrv" we're using Laravel + $con = DB::connection($DBConnectionUID); + $con->beginTransaction(); + } else { + // If the connection driver is "oracle" we are using the native oci8 functions + $con = Propel::getConnection($DBConnectionUID); + $con->begin(); } $blackList = System::getQueryBlackList(); From 4f18f357a5782bf21775bf321be2606ac59701f2 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Fri, 12 Jun 2020 18:20:05 -0400 Subject: [PATCH 20/24] PMCORE-1645 --- workflow/engine/classes/class.pmFunctions.php | 2 +- workflow/engine/src/ProcessMaker/Core/System.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workflow/engine/classes/class.pmFunctions.php b/workflow/engine/classes/class.pmFunctions.php index 149c9d85b..1c63ec182 100644 --- a/workflow/engine/classes/class.pmFunctions.php +++ b/workflow/engine/classes/class.pmFunctions.php @@ -478,7 +478,7 @@ function evaluateFunction($aGrid, $sExpresion) function PMFTotalCalculation($grid, $field, $function) { $systemConfiguration = Bootstrap::getSystemConfiguration(); - $floatPointNumber = $systemConfiguration['PMFTOTALCALCULATION_FLOATING_POINT_NUMBER']; + $floatPointNumber = $systemConfiguration['pmftotalcalculation_floating_point_number']; $function = strtolower($function); $totalRows = count($grid); $result = 0; diff --git a/workflow/engine/src/ProcessMaker/Core/System.php b/workflow/engine/src/ProcessMaker/Core/System.php index b703b95ac..ab57905aa 100644 --- a/workflow/engine/src/ProcessMaker/Core/System.php +++ b/workflow/engine/src/ProcessMaker/Core/System.php @@ -78,7 +78,7 @@ class System 'highlight_home_folder_refresh_time' => 10, 'highlight_home_folder_scope' => 'unassigned', // For now only this list is supported 'disable_advanced_search_case_title_fulltext' => 0, - 'PMFTOTALCALCULATION_FLOATING_POINT_NUMBER' => 10, + 'pmftotalcalculation_floating_point_number' => 10, 'report_table_batch_regeneration' => 1000 ]; From c8447e2de0a4769e340cf6224b6dbaee53a747b0 Mon Sep 17 00:00:00 2001 From: Andrea Adamczyk Date: Fri, 12 Jun 2020 18:30:39 -0400 Subject: [PATCH 21/24] PMCORE-1643 --- workflow/engine/classes/model/map/AppMessageMapBuilder.php | 2 +- workflow/engine/classes/model/om/BaseEmailServerPeer.php | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/workflow/engine/classes/model/map/AppMessageMapBuilder.php b/workflow/engine/classes/model/map/AppMessageMapBuilder.php index ea2715edd..eeaf03527 100644 --- a/workflow/engine/classes/model/map/AppMessageMapBuilder.php +++ b/workflow/engine/classes/model/map/AppMessageMapBuilder.php @@ -77,7 +77,7 @@ class AppMessageMapBuilder $tMap->addColumn('APP_MSG_TYPE_ID', 'AppMsgTypeId', 'int', CreoleTypes::TINYINT, false, null); - $tMap->addColumn('APP_MSG_SUBJECT', 'AppMsgSubject', 'string', CreoleTypes::VARCHAR, true, 150); + $tMap->addColumn('APP_MSG_SUBJECT', 'AppMsgSubject', 'string', CreoleTypes::VARCHAR, true, 998); $tMap->addColumn('APP_MSG_FROM', 'AppMsgFrom', 'string', CreoleTypes::VARCHAR, true, 100); diff --git a/workflow/engine/classes/model/om/BaseEmailServerPeer.php b/workflow/engine/classes/model/om/BaseEmailServerPeer.php index 7486104a6..a94d10809 100644 --- a/workflow/engine/classes/model/om/BaseEmailServerPeer.php +++ b/workflow/engine/classes/model/om/BaseEmailServerPeer.php @@ -579,9 +579,6 @@ abstract class BaseEmailServerPeer } } else { - if ($obj->isNew() || $obj->isColumnModified(EmailServerPeer::MESS_ENGINE)) - $columns[EmailServerPeer::MESS_ENGINE] = $obj->getMessEngine(); - } return BasePeer::doValidate(EmailServerPeer::DATABASE_NAME, EmailServerPeer::TABLE_NAME, $columns); From 2c79b4e6fb1b20a60b62fc2b04481a5c9e958062 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Fri, 12 Jun 2020 19:23:18 -0400 Subject: [PATCH 22/24] PMCORE-1646 --- .../workflow/engine/classes/PmFunctions/ExecuteQueryTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/workflow/engine/classes/PmFunctions/ExecuteQueryTest.php b/tests/unit/workflow/engine/classes/PmFunctions/ExecuteQueryTest.php index 4740f8c05..5bc7cccfb 100644 --- a/tests/unit/workflow/engine/classes/PmFunctions/ExecuteQueryTest.php +++ b/tests/unit/workflow/engine/classes/PmFunctions/ExecuteQueryTest.php @@ -18,9 +18,6 @@ use Tests\TestCase; */ class ExecuteQueryTest extends TestCase { - // todo: Please do not use "DatabaseTransactions", the executeQuery method opens another connection. - use DatabaseTransactions; - protected $nameSystemTables = "system-tables.ini"; protected $contentSystemTables = "tables = 'APPLICATION|APP_SEQUENCE|APP_DELEGATION|APP_DOCUMENT|APP_MESSAGE|APP_OWNER|CONFIGURATION|CONTENT|DEPARTMENT|DYNAFORM|GROUPWF|GROUP_USER|HOLIDAY|INPUT_DOCUMENT|ISO_COUNTRY|ISO_LOCATION|ISO_SUBDIVISION|LANGUAGE|LEXICO|OUTPUT_DOCUMENT|PROCESS|PROCESS_OWNER|REPORT_TABLE|REPORT_VAR|ROUTE|STEP|STEP_TRIGGER|SWIMLANES_ELEMENTS|TASK|TASK_USER|TRANSLATION|TRIGGERS|USERS|APP_THREAD|APP_DELAY|PROCESS_USER|SESSION|DB_SOURCE|STEP_SUPERVISOR|OBJECT_PERMISSION|CASE_TRACKER|CASE_TRACKER_OBJECT|CASE_CONSOLIDATED|STAGE|SUB_PROCESS|SUB_APPLICATION|LOGIN_LOG|USERS_PROPERTIES|ADDITIONAL_TABLES|FIELDS|SHADOW_TABLE|EVENT|GATEWAY|APP_EVENT|APP_CACHE_VIEW|DIM_TIME_DELEGATE|DIM_TIME_COMPLETE|APP_HISTORY|APP_FOLDER|FIELD_CONDITION|LOG_CASES_SCHEDULER|CASE_SCHEDULER|CALENDAR_DEFINITION|CALENDAR_BUSINESS_HOURS|CALENDAR_HOLIDAYS|CALENDAR_ASSIGNMENTS|PROCESS_CATEGORY|APP_NOTES|DASHLET|DASHLET_INSTANCE|APP_SOLR_QUEUE|SEQUENCES|SESSION_STORAGE|PROCESS_FILES|WEB_ENTRY|OAUTH_ACCESS_TOKENS|OAUTH_AUTHORIZATION_CODES|OAUTH_CLIENTS|OAUTH_REFRESH_TOKENS|OAUTH_SCOPES|PMOAUTH_USER_ACCESS_TOKENS|BPMN_PROJECT|BPMN_PROCESS|BPMN_ACTIVITY|BPMN_ARTIFACT|BPMN_DIAGRAM|BPMN_BOUND|BPMN_DATA|BPMN_EVENT|BPMN_FLOW|BPMN_GATEWAY|BPMN_LANESET|BPMN_LANE|BPMN_PARTICIPANT|BPMN_EXTENSION|BPMN_DOCUMENTATION|PROCESS_VARIABLES|APP_TIMEOUT_ACTION_EXECUTED|ADDONS_STORE|ADDONS_MANAGER|LICENSE_MANAGER|APP_ASSIGN_SELF_SERVICE_VALUE|APP_ASSIGN_SELF_SERVICE_VALUE_GROUP|LIST_INBOX|LIST_PARTICIPATED_HISTORY|LIST_PARTICIPATED_LAST|LIST_COMPLETED|LIST_PAUSED|LIST_CANCELED|LIST_MY_INBOX|LIST_UNASSIGNED|LIST_UNASSIGNED_GROUP|MESSAGE_TYPE|MESSAGE_TYPE_VARIABLE|EMAIL_SERVER|WEB_ENTRY_EVENT|MESSAGE_EVENT_DEFINITION|MESSAGE_EVENT_RELATION|MESSAGE_APPLICATION|ELEMENT_TASK_RELATION|ABE_CONFIGURATION|ABE_REQUESTS|ABE_RESPONSES|USR_REPORTING|PRO_REPORTING|DASHBOARD|DASHBOARD_INDICATOR|DASHBOARD_DAS_IND|CATALOG|SCRIPT_TASK|TIMER_EVENT|EMAIL_EVENT|NOTIFICATION_DEVICE|GMAIL_RELABELING|NOTIFICATION_QUEUE|PLUGINS_REGISTRY|APP_DATA_CHANGE_LOG|JOBS_PENDING|JOBS_FAILED|RBAC_PERMISSIONS|RBAC_ROLES|RBAC_ROLES_PERMISSIONS|RBAC_SYSTEMS|RBAC_USERS|RBAC_USERS_ROLES|RBAC_AUTHENTICATION_SOURCE|'"; protected $oldContentSystemTables = ""; From 9d9ce52fa8a54673c4d9f6964116b1a66b218e23 Mon Sep 17 00:00:00 2001 From: Roly Rudy Gutierrez Pinto Date: Fri, 12 Jun 2020 20:59:28 -0400 Subject: [PATCH 23/24] PMCORE-1619 Validations in the upload files related to cases notes before to move appDocument. --- tests/resources/testDocument.pdf | Bin 0 -> 7922 bytes .../ProcessMaker/BusinessModel/CasesTest.php | 51 ++++---- .../ValidationUploadedFilesTest.php | 39 +++++- .../translations/english/processmaker.en.po | 18 +++ workflow/engine/controllers/appProxy.php | 6 + workflow/engine/data/mysql/insert.sql | 3 + .../src/ProcessMaker/BusinessModel/Cases.php | 19 ++- .../Exception/CaseNoteUploadFile.php | 21 ++++ .../Validation/ValidationUploadedFiles.php | 114 ++++++++++++++++++ 9 files changed, 240 insertions(+), 31 deletions(-) create mode 100644 tests/resources/testDocument.pdf create mode 100644 workflow/engine/src/ProcessMaker/Exception/CaseNoteUploadFile.php diff --git a/tests/resources/testDocument.pdf b/tests/resources/testDocument.pdf new file mode 100644 index 0000000000000000000000000000000000000000..08299237a520f6e5f5bdb05ec7efe3e9f0da0efc GIT binary patch literal 7922 zcmai31yqz<*H-BPln@Y57#gV=reKti2I-PUU>G`w0g(_9kZuK$66uf>=?+0aKpF{Y zkPfBwpYh)B=J&1tUF)55&VKjV`J6Lr?aiejD=#Q41P5@nWwq6}y=zMcK!9MNvxOZ% zObn!gcCyCW0HHXF7D(RK0gJ|fehXjwGM*%B=w0dT`&(B_T+&lIk3Ve(e+)xg6; zekXdS?G6OUpQ`j#fDzjlpp%_Qi_i~W?Vgq!K0XS$M;}NCZ}`e;xOZPocxRG^nVxH4 z6zmZ|J!h*X-S@pcM)KV{$ZL2^P+K`9snKRR11nJhdP7Xiv~X8@O<=$JQxhDqU<$$% zK)M52&m_4-1Qn7OmTvbZgX%|P(-7OY?CsIrQNTuRs{+vkt?%Amop9t&SCefIV3S}AM{hY zm859hx;Su&Z63J9qaAD6Pml=$MtwiZR3tddWS9)R6y{bhZ={jO&nLw869n3%){?4y zYZT(oIdT3Z+%H8Z78NK^uTZvsc$^j#{S%Zqa5K6jR-s#ak@R7z@f%2eyMl!!NQ?gd zEC-0=xv2z1rBNwIvTb8wk#mo(23V79h3W25*y7>oYBK%Op^(!Rd9A0adwoFhQD9~q zko9rYl(gxcLRO&TO16rt@($u-^RC>mNO!=Ld3k_0Ni6DFTa75k!!$9%u~$nJ%MUrz zcbC-}8{(%T4lmMhol;pPdi3x($Gi@bw74e{e`C?P8v&_NTrlu_0qXog85dlZGmC=h zMH!5D-0$%XUij!s6bp(cGiGa^s^K_$*Pl1=^JqR;@n+Fnq0(S>wJ}t<*RK~Y=+kvy zKCFA7)i~tUrOWRpmqq{JpK!xJ2oR6DU& zr^eK6Q+kh;zAdBh48kfsZ94-M_sw-Ecs>?#WA7#NOqtm-b&cA_PFLly-2=ZF^}~mW zK>NbT*Wh!WCuhJSbQO#4qUJ~_X22dU#gf_ zsef_hX@LqeWBDS-lIv*LWjhtLba;qW-i_#0vVxzdujyexvy3{s=Tg@WdbTbTPO(*@ z(vpZ7>;j*Rm+F()=WNi#V`|0|wuLBrXqk7Qbh@fS&WTDcDkE_wEbmGP`>~}mWd4&h zr)TQ4wsXBI6=vdWu~syzuUeNqQp%b7-%5%JeGV7}Z`HZBIJ#JqhNLqMr$fH6FViJu}-E~4q>m>qS8`&pc&9@H>Zd?q; zAlF4L$qP`!1B0(Umh?SU*kpqDCuTYn-zL!CI(TwFBIKelUzbyQ`m%gaTR{oKl9-D*Kt-(Ycc zgtA}%n(W$^;cxzPNZtn|23z%uBPbRhr+-mH@$g43(b<5RPL$ToSoTYDdD)L-u_`ks zZKzT%GF{8Bdve0O) z`xbju8vjxe^p27``x2v1wBRL-^p$M0RH-+QS6E+XG+i`RP5?$5cWQvgDN!Zh_0Y>D@srSTQ#EYa#@}l3|n6 z5raLRZV^H&KD>ZSLrJKoR!PYM3l=uTDba=dW9~Kl*`{O0vOli|?w@bk=Rootgq0#Gqudjd+n*Ctsa}q+E?}{1(J{D~Y#RU3#L;m~Y}mZ={OF`}Y}5dL znAk*-ynNrYrgX8yyJ&sfo^e;O{=N4x!_Ms%45nS&n8-{&rx2`DRH{pTzgYuaGJoKs z`@FHJcZ2*HPkGi6jYC_qmQ#^tETs{m(nL!FA9pS z9H^tm__DvDp)|25t4g_&EN^1^xTV;TNB3oRj(eX_-pZ)?qUv+H`2r^D0Fa2@r03T2 zB)@}4nJp9JzUPP0MC^KS--Xrofa1z^Enk`24}8(px`fjv3VP2PyzSp-ioi--un`ZP zYd`-Ox|vv#i5@G@Ir%;vxU3y@EDEopg05jp_)N>QTyFO38NyCz8bBI4`WC!A?ZXVf z%3rdWm<6Peo9D5d1z?a!+ zQvrgXTvFzzxR&UDRp>nvbD@TR;Rd;jl|`^x`i&_Mo6-O?l6LFO81z*lQb=vB@-Q2F zO~WgSScqcLl_J_m$`PX1W_ChtAI2Q~b=R*~^IUJHfsBM)I{e^%*w5;-Fw!mCGApHE z^#ip3eZz5#l0<7i-7|Pj&yP3{JM|-@<9K&SSM6xc?PqzI_gFh-DhMeN3e}0w_3QKv zS2B`*oAv3n(LDKTgmKLkS{i{n0xq4GB*$q|@SjK}B1$?Q|slq1|HibDc0_9pH8COadcSk|E>qqK{l#@E_gB z#d?0b;Z;BOBXfT&M;RS%H7&gps7E@#{MoiGdA!f+w|j2=@$96opBCZ$_Hte6Wh)x2 zb0^=6N~@wLEer$e5zCmydQfw+@ZOtnXc7v^O9nhABaayH?+s30FDS3rE4#PwgF(fC zHffcc=2^|+1Gt2QnBTEq!=shm9(xkVKI@ORs;wl48VAa=xd$s7`>c{*Uq&~cIFdw; zgq5W&v%c4F`pUwF&Sp~8G|>U2Mgi1m3Z8` z;bm%PS9D0kL|J&FN7c*v%@9K=vmJi{01y`T;gmqGQ}<{owPfqcPNy%;MuJ%bVWN^* z!mB$^qB%Vkp6GEMWe`Ryx}+0k(mYmwa+!?jg>elb6WMDwWJ@k*m*SU%S4m~DhZ%&< zcftJry$(~{QuvgpPx$cXor&XuIRo2^KsUVNu8!uRE z*N-64xs|P0%f`)}fta789Q$>L+rimBr5-3ZWyjBF+aIU(maz9+f4q0^$bR|lGiLRU zSw{^YVDAz$M+yhS<+c)+sDyZf!#>M3E#*+t`!+X?&5cZ#$OS|M3+}ihv)rkW5f@)NpZ@-n3lm$8&b| z`^JN@`$UpW*_doyzPckHBjb_&0<-rY{U$xzRP&F;MGh$`XT4m0Os*Fbh_e>iAAd?c z-is&_t%BWWIk^qda~Jr2<2aJKZCR4KZjX7n$-VxdV!ug4uwG9iJG*IPZKp!IMx$gv zc$-acG3d+d^vVMw!6WW7d#yfJHi<_>jEjbfNZm22a)L3TtnJoZx+Ob!&Fr_vE6iU+ z&*&fZP`WiOCLb%nB}Y$m*S$VvovegVFO^7tAvwE3)?bYXSwW4bMw4h@ssL_TF%gAe z^&-o=L2zC-#h}WpXFjSy0m$!ARV|?}kyZA>{S|+t9jm|SEf5bkXTQ7 z(vG%PGt;sMK4J)Z^Y$`DK3eHHS?;h{(>f74kX`=<9`iMZA3+L2S|dH{h$UP>?vJac}M4`B6fXJHpZ~q z;@;Y!mV_J)uPavSfy8+_(9{=G!D;zeF&Z-M=xg5_yEyqW7P@6EuJl=p#kSsQOUiT8 zTwDnCB-W*s=Dpt8US=3t)}Z}XU4L0csCq!3&%b`rk~iR9&ZlpKD%>@LOOs80e!hy1 zeb+p4SOn`Nq7#uZ5qal|n*7@F;^RL=9j8r4@4sws{?3vi68de|!sQ8{2G1gQMOmY! zyFWUD3%YiKbtspJed+Se%Uh8oG?vVm(O4vgl#KSG9_cQXG>TIyZt;SWa6YdV(b*>v z`Oz;Gp|BWv<4bF@`Gk8sn)iX5q`4a5GcvtSPui8l8&|9RGAg+PPeyoaUwGV*{;cUo zA$ag=h8M%|nXcI?3c8bpiXM(4~RJVH>4i|lLGIgR5x~YGVDHp@}{o9%#9%B;M8O*1ZIe1!2!4-EMuVl1eh~E=dH4LTL zr?=J$NZc6@XL+yb7vT=!L3TO}g=z@!P(>BM8|c?lEvN&NOoggoUO%uTLgLH>0=2%q zD{At&o-cPNsniP}-h?w-YI4ThwJ&4k$as`Vakw$u$3M?6S}`@WabViE8tH}lXiOA9mVEL^Ye0Eodclo^%0Zq z;ozfp{F!#0+P%ehJm%aalfwwtji$=tz8(4KKC5BWThTGARSKjP2sgfwS|kFCCk=ll z&Gv3zK@$~se9t1he2uhhMt<1TTA#SaD`-T;c34T?mF7w^dF?ERooY$5+v}A$`sVgD@3>Wz{M4l~cfo+iOptcQJ4Mz}M*R7*oz zk8-ibNpA?hZlTXLzQ;rQlGJsD+P@}ie2d%A`H)wpQg%*_rhUQ5yxZ|9XmGH}3lSXt zpzlSk_1BRXCO_@Pne^gxU3PycE}@cIuXztov-WS-HiUn7E~X{$B>q;q&z}D=TJKGY zODs{Xy^sWyc_QhdIsFG%Cslu`=cl5CZvw$&i-)g+Sxzp$@wr`nL#DPJuD$pf0ieD_ z6+~A4Y@AKeqlJ|5iK8WX_3oOyDYL|9>fC;>ap`j2Dxx(b>e+|`RZow}-I=gLhOy~t zSoPIV;%6>%L)r6c?duu~a`gm8&nClkPNw`l86<9xA4=lvOUX+NqkgdQWa;u0N|+xX98FjPi5dfw?=_kIuCuP@RI8D8|I_sR%%XkpsA)dCt%AHR3; zwy+?<%x1N5wU;ZG%XB1p)BdaK8;RszQ_M%PCDPsEquzGgHM?nThc@vKEVo6yNYWe5 zS2w3%hr#wY#ZB`#0kg*(Mak%b{eSjKDGH~=j_t%$SRe=AI!YbmTJGD zoL$Z)$Xz5QMc}+*bZjcoGTWCvPwsv$k=$@)jBz$4+U+##z*MHBVQi!)-B@%X3VVBC zGHNkuHx$#!!PocfRRVxl^h-m+Ud)|u7b;7XXuUFf z4dp`Za&_HQ3~Gd%F@1gn$sJt}{!5h`U%$qew&5ah(Lc)EB7c^-bsoB)LGsQ{SXs0i z3S;Ynb;eciaZ5FGM>I%U3jgq{*jk`5=2%;2Cv7yw)(U_x?_0Y8VYms7Q(D^D(?}2w zg8~JiU?@-+A|e7riVB;6l(6OwwkRnlYX>wC3;;>Fq0mlPpeRxp0KzN7F9adN2mnaN z+(i*>Yi)zY?*o9ev1rG8KvCQlym(wk0Sy7-3-ek4T%G+tm3q-XUHH8L0TF@zKkWyT z*nX!rAVuf47}5JJjo2*JIwj4lVw0@v-xZV(FA%0s*j|+FVsn-RPjhcxIAQclM>Bgk zf!^FpYd~_`+QDiV-g?nDPdLCLpRHu7kssJ3=NTSq)cT~#$oh(QA&ItbU_#5movWku6J>!M&~ZW}>}}hUB_EB#D)`YGi6&DjeZu6EgT zhxd!h27^|ZZ&wDM{1>z!|BUJ%zyN8xTVU}h(ZRT*e^I5)-O%{d{BL~NV%)GYHs%<7 z-c`(hp9|x1t7mJ8wQ(~N!C?d;3FW+(A0Do=3$<)9&qqNajBM`3b z1=2x#Vom-Eyua-F6$}k?Ycxp19D|2546g&Eg?4jx$Dq(|Kw&(y7DyFsX={!@C3uSv z!VsVc5@G^Uz&N|R;QD9UxWJk_xw+s)pdNx`v~j1y!xn|sQji8I0kOECOGtoZa0cQu zxB>BvH?6;%1H4N7`Nj$Q!yGrjzia^fX#{S=A6DQ4t88oOW(53YDSj#A?2eDcUsT`> z{<{ttbKJ>xw#IpFjdSvs(*TgV3))Evg})?>ev<{#H!uV`xjQ)g_5;SrRxx+7=0&>+ zN~_=eTS))Yio-b}gKHJy?jSLFIeB?77%l<^i$K6&1QIvGa3c;3A`ppRIkI#|q5s1N zllhGYf#dzfkK=>k_9GC#zqtJfDDK74aV%gcjt-H-jZmDdUn+5S%i?;Lyt20-cn(~a zgkeyKFjx=_;{k(tZvG`6o*1+h01Om{0dSIje}D)$90~_o0e`0%h?%+s$b{@fRvo9oPol>I>xw# zEYJujhJ+%7g=K}2P&ix|DIyA&!7U&ND89QY3ljy)O91|V7Cd}qol!D2Xq3I1yCV<; z78Qoc3CqBcQZit9DH%8%DJ?7mhsq)4WpMw8xW67;R^71X80@cq02h~>x;)_j E0GmP4{{R30 literal 0 HcmV?d00001 diff --git a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php index aa55a5548..dcea64514 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php @@ -7,6 +7,7 @@ use G; use ProcessMaker\Model\Application; use ProcessMaker\Model\Delegation; use ProcessMaker\Model\Documents; +use ProcessMaker\Model\User; use RBAC; use Tests\TestCase; @@ -17,6 +18,21 @@ use Tests\TestCase; */ class CasesTest extends TestCase { + + /** + * Set up method. + */ + public function setUp() + { + parent::setUp(); + Delegation::truncate(); + Documents::truncate(); + Application::truncate(); + User::where('USR_ID', '=', 1) + ->where('USR_ID', '=', 2) + ->delete(); + } + /** * This checks the delete case * @@ -28,11 +44,11 @@ class CasesTest extends TestCase { // Set the RBAC global $RBAC; - $_SESSION['USER_LOGGED'] = '00000000000000000000000000000002'; - $RBAC = RBAC::getSingleton(PATH_DATA, session_id()); + $_SESSION['USER_LOGGED'] = G::generateUniqueID(); + $RBAC = RBAC::getSingleton(); $RBAC->initRBAC(); - $application = factory(Application::class)->create(); + $application = factory(Application::class)->create(['APP_INIT_USER' => G::generateUniqueID()]); // Tried to delete case $case = new Cases(); $case->deleteCase($application->APP_UID, $_SESSION['USER_LOGGED']); @@ -50,7 +66,7 @@ class CasesTest extends TestCase // Set the RBAC global $RBAC; $_SESSION['USER_LOGGED'] = '00000000000000000000000000000001'; - $RBAC = RBAC::getSingleton(PATH_DATA, session_id()); + $RBAC = RBAC::getSingleton(); $RBAC->initRBAC(); $application = factory(Application::class)->create(['APP_STATUS' => 'TO_DO']); @@ -70,38 +86,17 @@ class CasesTest extends TestCase { // Set the RBAC global $RBAC; - $_SESSION['USER_LOGGED'] = '00000000000000000000000000000001'; - $RBAC = RBAC::getSingleton(PATH_DATA, session_id()); + $_SESSION['USER_LOGGED'] = G::generateUniqueID(); + $RBAC = RBAC::getSingleton(); $RBAC->initRBAC(); - $application = factory(Application::class)->create(['APP_INIT_USER' => '00000000000000000000000000000002']); + $application = factory(Application::class)->create(['APP_INIT_USER' => G::generateUniqueID()]); // Tried to delete case $case = new Cases(); $case->deleteCase($application->APP_UID, $_SESSION['USER_LOGGED']); } /** - * Review the upload file related to the case notes, an return an exception when the array is empty - * - * @covers \ProcessMaker\BusinessModel\Cases::uploadFilesInCaseNotes() - * - * @test - * @expectedException Exception - */ - public function it_return_exception_in_upload_files_related_case_note() - { - $application = factory(Application::class)->create(); - factory(Delegation::class)->states('foreign_keys')->create([ - 'APP_NUMBER' => $application->APP_NUMBER, - 'APP_UID' => $application->APP_UID - ]); - // Upload the file - $case = new Cases(); - // Return an exception because the files does not exist - $case->uploadFilesInCaseNotes('00000000000000000000000000000001', $application->APP_UID, $filesReferences = []); - } - - /** * Review the upload file related to the case notes * * @covers \ProcessMaker\BusinessModel\Cases::uploadFilesInCaseNotes() diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFilesTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFilesTest.php index 21cb3b094..d4b8f2edf 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFilesTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFilesTest.php @@ -112,6 +112,43 @@ class ValidationUploadedFilesTest extends TestCase $this->assertEquals(0, $result->getStatus()); } + /** + * This test verify validation rules for files post in cases notes. + * @test + * @covers ::runRulesForPostFilesOfNote + */ + public function it_should_test_run_rules_for_post_files_of_note() + { + //assert for file has not exist + $file = [ + 'filename' => 'testDocument.pdf', + 'path' => "testDocument.pdf" + ]; + $validation = new ValidationUploadedFiles(); + $result = $validation->runRulesForPostFilesOfNote($file); + $this->assertTrue($result->fails()); + + //assert for file has not valid extension + $file = [ + 'filename' => 'projectData.json', + 'path' => PATH_TRUNK . "tests/resources/projectData.json" + ]; + $validation = new ValidationUploadedFiles(); + $result = $validation->runRulesForPostFilesOfNote($file); + $this->assertTrue($result->fails()); + + //assert the file exists and has valid extension + $file = [ + 'filename' => 'testDocument.pdf', + 'path' => PATH_TRUNK . "tests/resources/testDocument.pdf" + ]; + $validation = new ValidationUploadedFiles(); + $result = $validation->runRulesForPostFilesOfNote($file); + $this->assertFalse($result->fails()); + $this->assertEmpty($result->getMessage()); + $this->assertEquals(0, $result->getStatus()); + } + /** * It deletes the images created */ @@ -128,4 +165,4 @@ class ValidationUploadedFilesTest extends TestCase unlink(PATH_DATA . '1.PnG'); } } -} \ No newline at end of file +} diff --git a/workflow/engine/content/translations/english/processmaker.en.po b/workflow/engine/content/translations/english/processmaker.en.po index e0d1b1379..3817ab047 100644 --- a/workflow/engine/content/translations/english/processmaker.en.po +++ b/workflow/engine/content/translations/english/processmaker.en.po @@ -25307,6 +25307,12 @@ msgstr "Error: The application {0} is not canceled." msgid "The default configuration was not defined" msgstr "The default configuration was not defined" +# TRANSLATION +# LABEL/ID_THE_FILE_COULDNT_BE_UPLOADED +#: LABEL/ID_THE_FILE_COULDNT_BE_UPLOADED +msgid "The file couldn’t be uploaded please review the allowed files or contact your System Administrator." +msgstr "The file couldn’t be uploaded please review the allowed files or contact your System Administrator." + # TRANSLATION # LABEL/ID_THE_FILE_SIZE_IS_BIGGER_THAN_THE_MAXIMUM_ALLOWED #: LABEL/ID_THE_FILE_SIZE_IS_BIGGER_THAN_THE_MAXIMUM_ALLOWED @@ -27707,6 +27713,12 @@ msgstr "Yesterday" msgid "[LABEL/ID_YES_VALUE] Yes" msgstr "Yes" +# TRANSLATION +# LABEL/ID_YOUR_FILE_HAS_EXCEEDED +#: LABEL/ID_YOUR_FILE_HAS_EXCEEDED +msgid "Your file has exceeded the file maximum size that is {0}." +msgstr "Your file has exceeded the file maximum size that is {0}." + # TRANSLATION # LABEL/ID_YOUR_IMAGE_HAS_BEEN_SUCCESSFULLY #: LABEL/ID_YOUR_IMAGE_HAS_BEEN_SUCCESSFULLY @@ -27761,6 +27773,12 @@ msgstr "You do not select any user to import" msgid "you have an error" msgstr "you have an error" +# TRANSLATION +# LABEL/ID_YOU_UPLOADED_AN_UNSUPPORTED_FILE_EXTENSION +#: LABEL/ID_YOU_UPLOADED_AN_UNSUPPORTED_FILE_EXTENSION +msgid "You uploaded an unsupported file extension, please review the permitted files uploaded in the wiki of ProcessMaker for the cases notes." +msgstr "You uploaded an unsupported file extension, please review the permitted files uploaded in the wiki of ProcessMaker for the cases notes." + # TRANSLATION # LABEL/ID_ZIP_CODE #: LABEL/ID_ZIP_CODE diff --git a/workflow/engine/controllers/appProxy.php b/workflow/engine/controllers/appProxy.php index ab30a6028..f105d3b71 100644 --- a/workflow/engine/controllers/appProxy.php +++ b/workflow/engine/controllers/appProxy.php @@ -10,6 +10,7 @@ */ use ProcessMaker\BusinessModel\Cases as BmCases; +use ProcessMaker\Exception\CaseNoteUploadFile; use ProcessMaker\Model\AppNotes as Notes; use ProcessMaker\Model\Documents; use ProcessMaker\Util\DateTime; @@ -165,6 +166,11 @@ class AppProxy extends HttpProxyController try { $sendMail = intval($httpData->swSendMail); $response = $cases->addNote($appUid, $usrUid, $noteContent, $sendMail); + } catch (CaseNoteUploadFile $e) { + $response = new stdclass(); + $response->success = 'success'; + $response->message = $e->getMessage(); + die(G::json_encode($response)); } catch (Exception $error) { $response = new stdclass(); $response->success = 'success'; diff --git a/workflow/engine/data/mysql/insert.sql b/workflow/engine/data/mysql/insert.sql index 0a07360af..c34c84ad4 100644 --- a/workflow/engine/data/mysql/insert.sql +++ b/workflow/engine/data/mysql/insert.sql @@ -61109,6 +61109,7 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_THERE_PROBLEM_SENDING_EMAIL','en','There was a problem sending the email to','2016-04-08') , ( 'LABEL','ID_THE_APPLICATION_IS_NOT_CANCELED','en','Error: The application {0} is not canceled.','2016-06-15') , ( 'LABEL','ID_THE_DEFAULT_CONFIGURATION','en','The default configuration was not defined','2016-11-16') , +( 'LABEL','ID_THE_FILE_COULDNT_BE_UPLOADED','en','The file couldn’t be uploaded please review the allowed files or contact your System Administrator.','2020-06-12') , ( 'LABEL','ID_THE_FILE_SIZE_IS_BIGGER_THAN_THE_MAXIMUM_ALLOWED','en','The file size is bigger than the maximum allowed, the maximum size allowed is {0} Mbytes.','2019-02-26') , ( 'LABEL','ID_THE_MAXIMUM_VALUE_OF_THIS_FIELD_IS','en','The maximum value of this field is {0}.','2019-02-26') , ( 'LABEL','ID_THE_MIMETYPE_EXTENSION_ERROR','en','The mime type does not correspond to the permitted extension, please verify your file.','2018-10-02') , @@ -61519,6 +61520,7 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_YES','en','Yes','2014-01-15') , ( 'LABEL','ID_YESTERDAY','en','Yesterday','2014-01-15') , ( 'LABEL','ID_YES_VALUE','en','Yes','2014-01-15') , +( 'LABEL','ID_YOUR_FILE_HAS_EXCEEDED','en','Your file has exceeded the file maximum size that is 10MB.','2020-06-12') , ( 'LABEL','ID_YOUR_IMAGE_HAS_BEEN_SUCCESSFULLY','en','Your image has been successfully uploaded','2014-01-15') , ( 'LABEL','ID_YOUR_LICENSE','en','Your license','2014-09-18') , ( 'LABEL','ID_YOUR_PASSWORD_IS','en','Your password is','2014-01-15') , @@ -61528,6 +61530,7 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_YOU_DO_NOT_HAVE_PERMISSION','en','Error: You do not have permission.','2016-06-15') , ( 'LABEL','ID_YOU_DO_NOT_SELECT_ANY_USER_TO_IMPORT','en','You do not select any user to import','2015-09-15') , ( 'LABEL','ID_YOU_HAVE_ERROR','en','you have an error','2014-01-15') , +( 'LABEL','ID_YOU_UPLOADED_AN_UNSUPPORTED_FILE_EXTENSION','en','You uploaded an unsupported file extension, please review the permitted files uploaded in the wiki of ProcessMaker for the cases notes.','2020-06-12') , ( 'LABEL','ID_ZIP_CODE','en','Zip Code','2014-01-15') , ( 'LABEL','IMAGE_DETAIL','en','Image detail','2014-01-15') , ( 'LABEL','IMPORT_LANGUAGE_ERR_NO_WRITABLE','en','The XML forms directory is not writable','2014-01-15') , diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php index 571ab3b66..1182c5177 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php @@ -41,6 +41,7 @@ use ProcessMaker\BusinessModel\Task as BmTask; use ProcessMaker\BusinessModel\User as BmUser; use ProcessMaker\Core\System; use ProcessMaker\Exception\UploadException; +use ProcessMaker\Exception\CaseNoteUploadFile; use ProcessMaker\Model\Application as ModelApplication; use ProcessMaker\Model\AppNotes as Notes; use ProcessMaker\Model\Delegation; @@ -49,6 +50,7 @@ use ProcessMaker\Plugins\PluginRegistry; use ProcessMaker\Services\OAuth2\Server; use ProcessMaker\Util\DateTime as UtilDateTime; use ProcessMaker\Validation\ExceptionRestApi; +use ProcessMaker\Validation\ValidationUploadedFiles; use ProcessMaker\Validation\Validator as FileValidator; use ProcessPeer; use ProcessUser; @@ -3960,6 +3962,21 @@ class Cases } } + //rules validation + foreach ($files as $key => $value) { + $entry = [ + "filename" => $value['name'], + "path" => $value['tmp_name'] + ]; + $validator = ValidationUploadedFiles::getValidationUploadedFiles() + ->runRulesForPostFilesOfNote($entry); + if ($validator->fails()) { + Notes::where('NOTE_ID', '=', $noteId)->delete(); + $messageError = G::LoadTranslation('ID_THE_FILE_COULDNT_BE_UPLOADED'); + throw new CaseNoteUploadFile($messageError . ' ' . $validator->getMessage()); + } + } + // Get the delIndex related to the case $cases = new ClassesCases(); $delIndex = $cases->getCurrentDelegation($appUid); @@ -4007,8 +4024,6 @@ class Cases throw new UploadException($fileName['error']); } } - } else { - throw new Exception(G::LoadTranslation('ID_ERROR_UPLOAD_FILE_CONTACT_ADMINISTRATOR')); } return $response; diff --git a/workflow/engine/src/ProcessMaker/Exception/CaseNoteUploadFile.php b/workflow/engine/src/ProcessMaker/Exception/CaseNoteUploadFile.php new file mode 100644 index 000000000..246a1ea66 --- /dev/null +++ b/workflow/engine/src/ProcessMaker/Exception/CaseNoteUploadFile.php @@ -0,0 +1,21 @@ +validate(); } + /** + * Check if the loaded files comply with the validation rules, add here if you + * want more validation rules. + * Accept per argument an array or object that contains a "filename" and "path" values. + * The rules are verified in the order in which they have been added. + * + * @param array|object $file + * @return Validator + */ + public function runRulesForPostFilesOfNote($file) + { + $validator = new Validator(); + + //rule: file exists + $rule = $validator->addRule(); + $rule->validate($file, function($file) use($rule) { + $path = isset($file->path) ? $file->path : ""; + $filesystem = new Filesystem(); + if (!$filesystem->exists($path)) { + $rule->message(G::LoadTranslation('ID_NOT_EXISTS_FILE')); + return self::INVALID; + } + return self::VALID; + }) + ->status(400) + ->log(function($rule) { + /** + * Levels supported by MonologProvider is: + * 100 "DEBUG" + * 200 "INFO" + * 250 "NOTICE" + * 300 "WARNING" + * 400 "ERROR" + * 500 "CRITICAL" + * 550 "ALERT" + * 600 "EMERGENCY" + */ + Bootstrap::registerMonologPhpUploadExecution('phpUpload', $rule->getStatus(), $rule->getMessage(), $rule->getData()->filename); + }); + + //rule: extensions + $rule = $validator->addRule(); + $rule->validate($file, function($file) use($rule) { + $filesystem = new Filesystem(); + $extension = strtolower($filesystem->extension($file->filename)); + $extensions = [ + 'pdf', 'gif', 'jpg', 'png', 'doc', 'docx', 'xls', 'xlsx', 'txt', 'mp4', 'mpv', 'mpeg', 'mpg', 'mov' + ]; + if (!in_array($extension, $extensions)) { + $rule->message(G::LoadTranslation('ID_YOU_UPLOADED_AN_UNSUPPORTED_FILE_EXTENSION')); + return self::INVALID; + } + return self::VALID; + }) + ->status(400) + ->log(function($rule) { + /** + * Levels supported by MonologProvider is: + * 100 "DEBUG" + * 200 "INFO" + * 250 "NOTICE" + * 300 "WARNING" + * 400 "ERROR" + * 500 "CRITICAL" + * 550 "ALERT" + * 600 "EMERGENCY" + */ + Bootstrap::registerMonologPhpUploadExecution('phpUpload', $rule->getStatus(), $rule->getMessage(), $rule->getData()->filename); + }); + + //rule: file size + $rule = $validator->addRule(); + $rule->validate($file, function($file) use($rule) { + $path = isset($file->path) ? $file->path : ""; + $filesystem = new Filesystem(); + $limitSize = '10M'; + $size = $filesystem->size($path); + $phpShorthandByte = new PhpShorthandByte(); + $postMaxSizeBytes = $phpShorthandByte->valueToBytes($limitSize); + if ($size > $postMaxSizeBytes) { + $rule->message(G::LoadTranslation('ID_YOUR_FILE_HAS_EXCEEDED', [$limitSize])); + return self::INVALID; + } + return self::VALID; + }) + ->status(400) + ->log(function($rule) { + /** + * Levels supported by MonologProvider is: + * 100 "DEBUG" + * 200 "INFO" + * 250 "NOTICE" + * 300 "WARNING" + * 400 "ERROR" + * 500 "CRITICAL" + * 550 "ALERT" + * 600 "EMERGENCY" + */ + Bootstrap::registerMonologPhpUploadExecution('phpUpload', $rule->getStatus(), $rule->getMessage(), $rule->getData()->filename); + }); + + return $validator->validate(); + } + /** * Get the first error and call the argument function. * From b821c2ee85ce9af1a5fa2ce63e9db26249baab23 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Fri, 12 Jun 2020 21:07:20 -0400 Subject: [PATCH 24/24] PMCORE-1388 --- database/factories/DocumentsFactory.php | 2 +- workflow/engine/classes/WsBase.php | 3 ++- workflow/engine/classes/model/AppNotes.php | 2 +- workflow/engine/src/ProcessMaker/BusinessModel/Cases.php | 3 +-- workflow/engine/src/ProcessMaker/Util/helpers.php | 1 + 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/database/factories/DocumentsFactory.php b/database/factories/DocumentsFactory.php index acfafb0ce..f4ef0429f 100644 --- a/database/factories/DocumentsFactory.php +++ b/database/factories/DocumentsFactory.php @@ -28,7 +28,7 @@ $factory->define(\ProcessMaker\Model\Documents::class, function (Faker $faker) { ]; }); -// Create a dynaform with the foreign keys +// Create a document related to the case notes $factory->state(\ProcessMaker\Model\Documents::class, 'case_notes', function (Faker $faker) { return [ 'APP_DOC_UID' => G::generateUniqueID(), diff --git a/workflow/engine/classes/WsBase.php b/workflow/engine/classes/WsBase.php index 48330d1cd..dd84fcc3c 100644 --- a/workflow/engine/classes/WsBase.php +++ b/workflow/engine/classes/WsBase.php @@ -1,6 +1,7 @@ addNote($caseUid, $userUid, $note, $sendMail, $files); //Response diff --git a/workflow/engine/classes/model/AppNotes.php b/workflow/engine/classes/model/AppNotes.php index 823096a79..4ecece2d9 100644 --- a/workflow/engine/classes/model/AppNotes.php +++ b/workflow/engine/classes/model/AppNotes.php @@ -231,7 +231,7 @@ class AppNotes extends BaseAppNotes } if (!empty($attachFileLinks)) { - $body = $body . "
" . G::LoadTranslation('ID_ATTACHED_FILES') . ": 
" . implode("
", $attachFileLinks); + $body = $body . "
" . G::LoadTranslation('ID_ATTACHED_FILES') . ": 
" . implode("
", $attachFileLinks); } $users = new Users(); $recipientsArray = explode(",", $noteRecipients); diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php index 372ac5b14..b021baa02 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php @@ -3858,7 +3858,6 @@ class Cases * @param array $files * * @return array - * @throws Exception */ public function addNote($appUid, $userUid, $note, $sendMail = false, $files = []) { @@ -3925,7 +3924,7 @@ class Cases * @param string $userUid * @param string $appUid * @param array $filesReferences - * @param string $appDocUid + * @param int $noteId * * @return array * @throws Exception diff --git a/workflow/engine/src/ProcessMaker/Util/helpers.php b/workflow/engine/src/ProcessMaker/Util/helpers.php index e63d20a30..dac3f1644 100644 --- a/workflow/engine/src/ProcessMaker/Util/helpers.php +++ b/workflow/engine/src/ProcessMaker/Util/helpers.php @@ -609,6 +609,7 @@ function getMysqlVersion() * @param string $appUid * @param string $appDocUid * @param int $version + * @param bool $upload * * @return string */