diff --git a/.env.example b/.env.example index 3f7c742622..1b2468d570 100644 --- a/.env.example +++ b/.env.example @@ -44,4 +44,6 @@ SAML_SP_DESTINATION="https://keycloak.processmaker.net/realms/realmname/broker/s OPEN_AI_NLQ_TO_PMQL_ENABLED=true OPEN_AI_PROCESS_TRANSLATIONS_ENABLED=true OPEN_AI_SECRET="sk-O2D..." -AI_MICROSERVICE_HOST="http://localhost:8010" \ No newline at end of file +AI_MICROSERVICE_HOST="http://localhost:8010" +PROCESS_REQUEST_ERRORS_RATE_LIMIT=1 +PROCESS_REQUEST_ERRORS_RATE_LIMIT_DURATION=86400 diff --git a/.github/workflows/deploy-pm4.yml b/.github/workflows/deploy-pm4.yml index d99fc6baa8..e354b77c34 100644 --- a/.github/workflows/deploy-pm4.yml +++ b/.github/workflows/deploy-pm4.yml @@ -194,9 +194,9 @@ jobs: run: | cd pm4-k8s-distribution/images/pm4-tools docker pull $IMAGE - docker-compose down -v - docker-compose build phpunit - docker-compose run phpunit + docker compose down -v + docker compose build phpunit + docker compose run phpunit CONTAINER_ID=$(docker ps -a --filter ancestor=pm4-eks-phpunit:latest --format "{{.ID}}" | head -n 1) docker cp $CONTAINER_ID:/opt/processmaker/coverage.xml coverage.xml - name: Archive code coverage diff --git a/.github/workflows/scripts/translate.py b/.github/workflows/scripts/translate.py index 0097e2562a..5b845518c7 100644 --- a/.github/workflows/scripts/translate.py +++ b/.github/workflows/scripts/translate.py @@ -53,7 +53,7 @@ def main(): while retry_count < max_retries: try: start_time = time.time() - response=client.chat.completions.create(model='gpt-4', + response=client.chat.completions.create(model='gpt-4o', messages=[ { "role": "user", diff --git a/ProcessMaker/Console/Migration/ExtendedMigrateCommand.php b/ProcessMaker/Console/Migration/ExtendedMigrateCommand.php new file mode 100644 index 0000000000..f83b03436b --- /dev/null +++ b/ProcessMaker/Console/Migration/ExtendedMigrateCommand.php @@ -0,0 +1,18 @@ +flush(); + + parent::handle(); + } +} diff --git a/ProcessMaker/Events/RequestError.php b/ProcessMaker/Events/RequestError.php index d28da908ef..a43c91b898 100644 --- a/ProcessMaker/Events/RequestError.php +++ b/ProcessMaker/Events/RequestError.php @@ -4,8 +4,9 @@ use Carbon\Carbon; use Illuminate\Foundation\Events\Dispatchable; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\RateLimiter; use ProcessMaker\Contracts\SecurityLogEventInterface; -use ProcessMaker\Models\Process; use ProcessMaker\Models\ProcessRequest; class RequestError implements SecurityLogEventInterface @@ -57,4 +58,31 @@ public function getEventName(): string { return 'RequestError'; } + + /** + * Dispatch the event if the process request is not rate limited + * + * @param ProcessRequest $request + * @param string $error + * + * @return bool + */ + public static function dispatchIfNotRateLimited(ProcessRequest $request, string $error): bool + { + $key = 'process-request-errors:' . $request->getKey(); + $limit = config('app.process_request_errors_rate_limit', 1); + Log::info("Rate limit is set to {$limit} for process request errors."); + + if (RateLimiter::tooManyAttempts($key, $limit)) { + Log::warning("Process {$request->id} has reached the request error limit for today."); + return false; + } + + $duration = config('app.process_request_errors_rate_limit_duration', 86400); + Log::info("Rate limit duration is set to {$duration} for process request errors."); + RateLimiter::hit($key, $duration); + event(new static($request, $error)); + + return true; + } } diff --git a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php index 40b2e22594..5c1277340c 100644 --- a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php +++ b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php @@ -25,6 +25,7 @@ use ProcessMaker\Http\Resources\ProcessRequests as ProcessRequestResource; use ProcessMaker\Jobs\CancelRequest; use ProcessMaker\Jobs\TerminateRequest; +use ProcessMaker\Managers\DataManager; use ProcessMaker\Models\Comment; use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequestToken; @@ -261,12 +262,12 @@ public function show(ProcessRequest $request) /** * Retry the service, script, and other tasks for a given request * - * @param \ProcessMaker\Models\ProcessRequest $request - * @param \Illuminate\Http\Request $httpRequest + * @param ProcessRequest $request + * @param Request $httpRequest * - * @return \Illuminate\Http\JsonResponse - * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Illuminate\Validation\ValidationException + * @return JsonResponse + * @throws AuthorizationException + * @throws ValidationException */ public function retry(ProcessRequest $request, Request $httpRequest): JsonResponse { @@ -592,7 +593,7 @@ private function checkDomain($domain, $whitelist) * Cancel all tokens of request. * * @param ProcessRequest $request - * @throws \Throwable + * @throws Throwable */ private function cancelRequestToken(ProcessRequest $request) { @@ -608,7 +609,7 @@ private function cancelRequestToken(ProcessRequest $request) * Manually complete a request * * @param ProcessRequest $request - * @throws \Throwable + * @throws Throwable */ private function completeRequest(ProcessRequest $request) { @@ -739,4 +740,44 @@ public function getRequestToken(Request $httpRequest, ProcessRequest $request) return new ApiResource($token); } + + /** + * Retrieve the screens requested for a given process request. + * + * @param Request $httpRequest + * @param ProcessRequest $request + * + * @return ApiCollection + */ + public function screenRequested(Request $httpRequest, ProcessRequest $request) + { + $query = ProcessRequestToken::query(); + $query->select('id', 'element_id', 'process_id', 'process_request_id', 'data') + ->where('process_request_id', $request->id) + ->whereNotIn('element_type', ['startEvent', 'end_event', 'scriptTask']) + ->where('status', 'CLOSED') + ->orderBy('completed_at'); + + $response = + $query->orderBy( + $httpRequest->input('order_by', 'id'), + $httpRequest->input('order_direction', 'asc') + )->paginate($httpRequest->input('per_page', 10)); + + $response->getCollection()->transform(function ($token) { + $definition = $token->getDefinition(); + if (array_key_exists('screenRef', $definition)) { + $screen = $token->getScreenVersion(); + if ($screen) { + $dataManager = new DataManager(); + $screen->data = $dataManager->getData($token, true); + $screen->screen_id = $screen->id; + + return $screen; + } + } + }); + + return new ApiCollection($response); + } } diff --git a/ProcessMaker/Http/Controllers/Api/ScreenController.php b/ProcessMaker/Http/Controllers/Api/ScreenController.php index 382ac82235..db3a538211 100644 --- a/ProcessMaker/Http/Controllers/Api/ScreenController.php +++ b/ProcessMaker/Http/Controllers/Api/ScreenController.php @@ -272,9 +272,11 @@ public function store(Request $request) */ public function update(Screen $screen, Request $request) { + $lastVersion = $screen->getDraftOrPublishedLatestVersion(); $request->validate(Screen::rules($screen)); $screen->fill($request->input()); $original = $screen->getOriginal(); + $screen->config = $lastVersion->config; $screen->saveOrFail(); $screen->syncProjectAsset($request, Screen::class); diff --git a/ProcessMaker/Http/Controllers/HomeController.php b/ProcessMaker/Http/Controllers/HomeController.php index ccdddf3126..df81ab10f9 100644 --- a/ProcessMaker/Http/Controllers/HomeController.php +++ b/ProcessMaker/Http/Controllers/HomeController.php @@ -12,15 +12,8 @@ class HomeController extends Controller public function index(Request $request) { if (Auth::check()) { - $isMobile = ( - isset($_SERVER['HTTP_USER_AGENT']) && MobileHelper::isMobile($_SERVER['HTTP_USER_AGENT']) - ) ? true : false; - // If is mobile redirect to view mobile request - if ($isMobile) { - return redirect('/requests'); - } // Redirect to home dynamic only if the package was enable - if (!$isMobile && hasPackage('package-dynamic-ui')) { + if (hasPackage('package-dynamic-ui')) { $user = \Auth::user(); $homePage = \ProcessMaker\Package\PackageDynamicUI\Models\DynamicUI::getHomePage($user); diff --git a/ProcessMaker/Http/Controllers/Process/ScreenController.php b/ProcessMaker/Http/Controllers/Process/ScreenController.php index 51e698158b..2e9de31e7d 100644 --- a/ProcessMaker/Http/Controllers/Process/ScreenController.php +++ b/ProcessMaker/Http/Controllers/Process/ScreenController.php @@ -84,7 +84,20 @@ public function edit(Screen $screen) $addons = $this->getPluginAddons('edit', compact(['screen'])); $assignedProjects = json_decode($screen->projects, true); - return view('processes.screens.edit', compact('screen', 'addons', 'assignedProjects')); + $lastDraftOrPublishedVersion = $screen->getDraftOrPublishedLatestVersion(); + + $isDraft = 0; + if ($lastDraftOrPublishedVersion) { + $isDraft = $lastDraftOrPublishedVersion->draft; + } + + return view('processes.screens.edit', compact( + 'screen', + 'addons', + 'assignedProjects', + 'isDraft' + ) + ); } /** diff --git a/ProcessMaker/Http/Controllers/RequestController.php b/ProcessMaker/Http/Controllers/RequestController.php index 0c169d04ee..86c0a77055 100644 --- a/ProcessMaker/Http/Controllers/RequestController.php +++ b/ProcessMaker/Http/Controllers/RequestController.php @@ -176,7 +176,6 @@ public function show(ProcessRequest $request, Media $mediaItems) $files = \ProcessMaker\Models\Media::getFilesRequest($request); $canPrintScreens = $this->canUserPrintScreen($request); - $screenRequested = $canPrintScreens ? $request->getScreensRequested() : []; $manager = app(ScreenBuilderManager::class); event(new ScreenBuilderStarting($manager, ($request->summary_screen) ? $request->summary_screen->type : 'FORM')); @@ -203,7 +202,6 @@ public function show(ProcessRequest $request, Media $mediaItems) 'canRetry', 'manager', 'canPrintScreens', - 'screenRequested', 'addons', 'isProcessManager', 'eligibleRollbackTask', @@ -222,7 +220,6 @@ public function show(ProcessRequest $request, Media $mediaItems) 'canRetry', 'manager', 'canPrintScreens', - 'screenRequested', 'addons', 'dataActionsAddons', 'isProcessManager', diff --git a/ProcessMaker/Http/Controllers/TaskController.php b/ProcessMaker/Http/Controllers/TaskController.php index f66d143828..cb8ccdc17d 100644 --- a/ProcessMaker/Http/Controllers/TaskController.php +++ b/ProcessMaker/Http/Controllers/TaskController.php @@ -165,8 +165,12 @@ public function edit(ProcessRequestToken $task, string $preview = '') public function quickFillEdit(ProcessRequestToken $task) { + $screenVersion = $task->getScreenVersion(); + $screenFields = $screenVersion ? $screenVersion->screenFilteredFields() : []; + return view('tasks.editQuickFill', [ 'task' => $task, + 'screenFields' => $screenFields, ]); } } diff --git a/ProcessMaker/Http/Resources/ProcessRequests.php b/ProcessMaker/Http/Resources/ProcessRequests.php index 41061491f1..f0f87773a8 100644 --- a/ProcessMaker/Http/Resources/ProcessRequests.php +++ b/ProcessMaker/Http/Resources/ProcessRequests.php @@ -9,6 +9,7 @@ class ProcessRequests extends ApiResource public function toArray($request) { $array = parent::toArray($request); + $array['process_version_alternative'] = $this->processVersionAlternative; $include = explode(',', $request->input('include', '')); if (in_array('data', $include)) { diff --git a/ProcessMaker/Http/Resources/Task.php b/ProcessMaker/Http/Resources/Task.php index 186c6ab89f..2999ba7c40 100644 --- a/ProcessMaker/Http/Resources/Task.php +++ b/ProcessMaker/Http/Resources/Task.php @@ -53,7 +53,7 @@ public function toArray($request) $array = parent::toArray($request); $include = explode(',', $request->input('include', '')); - $this->process = Process::findOrFail($this->processRequest->process_id); + $this->process = $this->resource->process; foreach ($this->includeMethods as $key) { if (!in_array($key, $include)) { @@ -94,19 +94,19 @@ private function addAssignableUsers(&$array, $include) || count($array['assignable_users']) < 1; if (in_array('assignableUsers', $include) && $needToRecalculateAssignableUsers) { $definition = $this->getDefinition(); - if (isset($definition['assignment']) && $definition['assignment'] == 'self_service') { + $config = json_decode($definition['config'], true) ?: []; + $isSelfService = $config['selfService'] ?? false; + if ($isSelfService) { $users = []; $selfServiceUsers = $array['self_service_groups']['users']; $selfServiceGroups = $array['self_service_groups']['groups']; if ($selfServiceUsers !== ['']) { - $assignedUsers = $this->getAssignedUsers($selfServiceUsers); - $users = array_unique(array_merge($users, $assignedUsers)); + $users = $this->addActiveAssignedUsers($selfServiceUsers, $users); } if ($selfServiceGroups !== ['']) { - $assignedUsers = $this->getAssignedGroupMembers($selfServiceGroups); - $users = array_unique(array_merge($users, $assignedUsers)); + $users = $this->addActiveAssignedGroupMembers($selfServiceGroups, $users); } $array['assignable_users'] = $users; } @@ -127,26 +127,39 @@ private function loadUserRequestPermission(ProcessRequest $request, User $user, return $permissions; } - private function getAssignedUsers($users) + /** + * Add the active users to the list of assigned users + * + * @param array $users List of users ids + * @param array $assignedUsers List of assigned users + * + * @return array List of assigned users with additional active users + */ + private function addActiveAssignedUsers(array $users, array $assignedUsers) { - foreach ($users as $user) { - $assignedUsers[] = User::where('status', 'ACTIVE')->where('id', $user)->first(); + $users = array_unique($users); + $userChunks = array_chunk($users, 1000); + foreach ($userChunks as $chunk) { + $activeUsers = User::select('id') + ->whereNotIn('status', Process::NOT_ASSIGNABLE_USER_STATUS) + ->whereIn('id', $chunk) + ->pluck('id')->toArray(); + $assignedUsers = array_merge($assignedUsers,$activeUsers); } return $assignedUsers; } - private function getAssignedGroupMembers($groups) + /** + * Add the active group members to the list of assigned users + * + * @param array $groups List of group ids + * @param array $assignedUsers List of assigned users + * @return array List of assigned users with additional active users + */ + private function addActiveAssignedGroupMembers(array $groups, array $assignedUsers) { - \Log::debug('groups', ['groups' =>$groups]); - foreach ($groups as $group) { - $groupMembers = GroupMember::where('group_id', $group)->get(); - foreach ($groupMembers as $member) { - $assignedUsers[] = User::where('status', 'ACTIVE')->where('id', $member->member_id)->first(); - } - } - - return $assignedUsers; + return (new Process)->getConsolidatedUsers($groups, $assignedUsers); } private function getData() diff --git a/ProcessMaker/ImportExport/Exporters/ProcessLaunchpadExporter.php b/ProcessMaker/ImportExport/Exporters/ProcessLaunchpadExporter.php index bc4205893e..8420a9a3e8 100644 --- a/ProcessMaker/ImportExport/Exporters/ProcessLaunchpadExporter.php +++ b/ProcessMaker/ImportExport/Exporters/ProcessLaunchpadExporter.php @@ -12,7 +12,9 @@ class ProcessLaunchpadExporter extends ExporterBase public function export(): void { - $this->addDependent('user', $this->model->user, UserExporter::class); + if ($this->model->user) { + $this->addDependent('user', $this->model->user, UserExporter::class); + } $properties = json_decode($this->model->properties, true); $screenUuid = $properties['screen_uuid'] ?? null; diff --git a/ProcessMaker/Jobs/SyncGuidedTemplates.php b/ProcessMaker/Jobs/SyncGuidedTemplates.php index 833806a0b4..793774716d 100644 --- a/ProcessMaker/Jobs/SyncGuidedTemplates.php +++ b/ProcessMaker/Jobs/SyncGuidedTemplates.php @@ -151,6 +151,10 @@ private function importTemplate($template, $config, $guidedTemplateCategoryId) private function buildTemplateUrl($config, $templatePath) { // Build the URL for a template based on the configuration and template path + if (empty($templatePath)) { + return null; + } + return $config['base_url'] . $config['template_repo'] . '/' . $config['template_branch'] . '/' . @@ -273,9 +277,11 @@ private function importTemplateAssets($template, $config, $mediaCollectionName, // Build asset urls $templateIconUrl = $this->buildTemplateUrl($config, $template['assets']['icon']); $templateCardBackgroundUrl = $this->buildTemplateUrl($config, $template['assets']['card-background']); + $templateListIconUrl = $this->buildTemplateUrl($config, $template['assets']['list-icon']); // Import template assets and associate with the media collection $this->importMedia($templateIconUrl, 'icon', $mediaCollectionName, $guidedTemplate); $this->importMedia($templateCardBackgroundUrl, 'cardBackground', $mediaCollectionName, $guidedTemplate); + $this->importMedia($templateListIconUrl, 'listIcon', $mediaCollectionName, $guidedTemplate); if (!empty($template['assets']['launchpad']['process-card-background'])) { $templateProcessCardBackgroundUrl = @@ -300,7 +306,12 @@ private function importTemplateAssets($template, $config, $mediaCollectionName, private function importMedia($assetUrl, $customProperty, $mediaCollectionName, $guidedTemplate) { // Import a media asset and associate it with the media collection - $guidedTemplate->addMediaFromUrl($assetUrl)->withCustomProperties(['media_type' => $customProperty])->toMediaCollection($mediaCollectionName); + if (!is_null($assetUrl)) { + $guidedTemplate + ->addMediaFromUrl($assetUrl) + ->withCustomProperties(['media_type' => $customProperty]) + ->toMediaCollection($mediaCollectionName); + } } private function checkForTemplateChanges($template) diff --git a/ProcessMaker/Models/Process.php b/ProcessMaker/Models/Process.php index efa6d91c04..cfd589e4e0 100644 --- a/ProcessMaker/Models/Process.php +++ b/ProcessMaker/Models/Process.php @@ -164,6 +164,8 @@ class Process extends ProcessMakerModel implements HasMedia, ProcessModelInterfa const ASSIGNMENT_PROCESS = 'Assignment process'; + const NOT_ASSIGNABLE_USER_STATUS = ['INACTIVE', 'OUT_OF_OFFICE']; + protected $connection = 'processmaker'; /** @@ -758,6 +760,16 @@ private function getNextUserFromProcessVariable($activity, $token) // We need to remove inactive users. $users = User::whereIn('id', array_unique($assignedUsers))->where('status', 'ACTIVE')->pluck('id')->toArray(); + // user in OUT_OF_OFFICE + $outOfOffice = User::whereIn('id', array_unique($assignedUsers))->where('status', 'OUT_OF_OFFICE')->get(); + + foreach ($outOfOffice as $user) { + $delegation = $user->delegationUser()->pluck('id')->toArray(); + if ($delegation) { + $users[] = $delegation[0]; + } + } + foreach ($assignedGroups as $groupId) { // getConsolidatedUsers already removes inactive users $this->getConsolidatedUsers($groupId, $users); @@ -1002,33 +1014,46 @@ public function getAssignableUsers($processTaskUuid) /** * Get a consolidated list of users within groups. * - * @param binary $group_id + * @param mixed $group_id * @param array $users * * @return array */ - public function getConsolidatedUsers($group_id, array &$users) - { - $users = array_unique(array_merge( - GroupMember::where([ - ['group_id', '=', $group_id], - ['member_type', '=', User::class], - ]) - ->leftjoin('users', 'users.id', '=', 'group_members.member_id') - ->where('users.status', 'ACTIVE')->pluck('member_id')->toArray(), - $users - )); - $groupMembers = GroupMember::where([ - ['group_id', '=', $group_id], - ['member_type', '=', Group::class], - ]) + public function getConsolidatedUsers($groupOrGroups, array &$users) + { + $isArray = is_array($groupOrGroups); + if ($isArray) { + $groupOrGroups = array_unique($groupOrGroups); + } + // Add the users from the groups + GroupMember::select('member_id') + ->where('member_type', User::class) + ->when($isArray, function ($query) use ($groupOrGroups) { + $query->whereIn('group_id', $groupOrGroups); + }, function ($query) use ($groupOrGroups) { + $query->where('group_id', $groupOrGroups); + }) + ->leftjoin('users', 'users.id', '=', 'group_members.member_id') + ->whereNotIn('users.status', Process::NOT_ASSIGNABLE_USER_STATUS) + ->chunk(1000, function ($members) use (&$users) { + $userIds = $members->pluck('member_id')->toArray(); + $users = array_unique(array_merge($users, $userIds)); + }); + + // Add the users from the subgroups + GroupMember::select('member_id') + ->where('member_type', Group::class) + ->when($isArray, function ($query) use ($groupOrGroups) { + $query->whereIn('group_id', $groupOrGroups); + }, function ($query) use ($groupOrGroups) { + $query->where('group_id', $groupOrGroups); + }) ->leftjoin('groups', 'groups.id', '=', 'group_members.member_id') ->where('groups.status', 'ACTIVE') - ->pluck('member_id'); - - foreach ($groupMembers as $groupMember) { - $this->getConsolidatedUsers($groupMember, $users); - } + ->chunk(1000, function ($members) use (&$users) { + $groupIds = $members->pluck('member_id')->toArray(); + $users = $this->addActiveAssignedGroupMembers($groupIds, $users); + }); return $users; } @@ -1151,35 +1176,31 @@ public function manageCustomRoutes() foreach ($this->start_events as $startEvent) { $webEntryProperties = (isset($startEvent['config']) && isset(json_decode($startEvent['config'])->web_entry) ? json_decode($startEvent['config'])->web_entry : null); - if ($webEntryProperties && isset($webEntryProperties->webentryRouteConfig)) { - switch ($webEntryProperties->webentryRouteConfig->urlType) { - case 'standard-url': - $this->deleteUnusedCustomRoutes( - $webEntryProperties->webentryRouteConfig->firstUrlSegment, - $webEntryProperties->webentryRouteConfig->processId, - $webEntryProperties->webentryRouteConfig->nodeId - ); - break; + if (!($webEntryProperties && isset($webEntryProperties->webentryRouteConfig))) { + continue; + } - default: - if ($webEntryProperties->webentryRouteConfig->firstUrlSegment !== '') { - $webentryRouteConfig = $webEntryProperties->webentryRouteConfig; - try { - WebentryRoute::updateOrCreate( - [ - 'process_id' => $this->id, - 'node_id' => $webentryRouteConfig->nodeId, - ], - [ - 'first_segment' => $webentryRouteConfig->firstUrlSegment, - 'params' => $webentryRouteConfig->parameters, - ] - ); - } catch (\Exception $e) { - \Log::info('*** Error: ' . $e->getMessage()); - } - } - break; + if ($webEntryProperties->webentryRouteConfig->urlType === 'standard-url') { + $this->deleteUnusedCustomRoutes( + $webEntryProperties->webentryRouteConfig->firstUrlSegment, + $webEntryProperties->webentryRouteConfig->processId, + $webEntryProperties->webentryRouteConfig->nodeId + ); + } elseif ($webEntryProperties->webentryRouteConfig->firstUrlSegment !== '') { + $webentryRouteConfig = $webEntryProperties->webentryRouteConfig; + try { + WebentryRoute::updateOrCreate( + [ + 'process_id' => $this->id, + 'node_id' => $webentryRouteConfig->nodeId, + ], + [ + 'first_segment' => $webentryRouteConfig->firstUrlSegment, + 'params' => $webentryRouteConfig->parameters, + ] + ); + } catch (Exception $e) { + \Log::info('*** Error: ' . $e->getMessage()); } } } @@ -1250,7 +1271,7 @@ private function getStartEventPermissions(User $user) /** * Process events relationship. * - * @return \ProcessMaker\Models\ProcessEvents + * @return ProcessEvents */ public function events() { @@ -1287,7 +1308,7 @@ public function launchpad() /** * Assignments of the process. * - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ public function assignments() { @@ -1616,7 +1637,7 @@ private function validateSchema(BpmnDocument $document) private function deleteUnusedCustomRoutes($url, $processId, $nodeId) { // Delete unused custom routes - $customRoute = webentryRoute::where('process_id', $processId)->where('node_id', $nodeId)->first(); + $customRoute = WebentryRoute::where('process_id', $processId)->where('node_id', $nodeId)->first(); if ($customRoute) { $customRoute->delete(); } @@ -1751,9 +1772,9 @@ public function scopeFilter($query, $filterStr) ->orWhere('processes.description', 'like', $filter) ->orWhere('processes.status', '=', $filterStr) ->orWhereHas('user', function ($query) use ($filter) { - $query->where('firstname', 'like', $filter) - ->orWhere('lastname', 'like', $filter); - }) + $query->where('firstname', 'like', $filter) + ->orWhere('lastname', 'like', $filter); + }) ->orWhereIn('processes.id', function ($qry) use ($filter) { $qry->select('assignable_id') ->from('category_assignments') diff --git a/ProcessMaker/Models/ProcessMakerModel.php b/ProcessMaker/Models/ProcessMakerModel.php index 81d4d89ee6..ee396e918d 100644 --- a/ProcessMaker/Models/ProcessMakerModel.php +++ b/ProcessMaker/Models/ProcessMakerModel.php @@ -5,6 +5,8 @@ use DateTimeInterface; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Cache; /** * Base class that all models should extend from. @@ -13,6 +15,8 @@ class ProcessMakerModel extends Model { use HasFactory; + const MIGRATION_COLUMNS_CACHE_KEY = 'migration_columns'; + /** * Prepare a date for array / JSON serialization. * @@ -23,4 +27,31 @@ protected function serializeDate(DateTimeInterface $date) { return $date->format('Y-m-d H:i:s'); } + + public function scopeExclude($query, array $columns) + { + if (!empty($columns)) { + return $query; + } + + $columnsToShow = array_diff($this->getTableColumns(), $columns); + $columnsToShow = array_map(function ($column) { + return $this->table . '.' . $column; + }, $columnsToShow); + + return $query->select($columnsToShow); + } + + private function getTableColumns() + { + $key = 'MigrMod:' . $this->getTable(); + + return Cache::tags(static::MIGRATION_COLUMNS_CACHE_KEY)->rememberForever( + $key, function () { + return $this->getConnection() + ->getSchemaBuilder() + ->getColumnListing($this->getTable()); + } + ); + } } diff --git a/ProcessMaker/Models/ProcessRequest.php b/ProcessMaker/Models/ProcessRequest.php index 5ab2a1f681..613b653ebf 100644 --- a/ProcessMaker/Models/ProcessRequest.php +++ b/ProcessMaker/Models/ProcessRequest.php @@ -952,21 +952,28 @@ public function getMedia(string $collectionName = 'default', $filters = []): Col return Media::getFilesRequest($this); } - public function getErrors() + public function getErrors($limit = 10) { if ($this->errors) { - return $this->errors; + return array_slice($this->errors, -$limit); } // select tokens with errors return $this->tokens() ->select('token_properties->error as message', 'created_at', 'element_name') ->where('status', '=', ActivityInterface::TOKEN_STATE_FAILING) - ->limit(10) + ->limit($limit) ->get() ->toArray(); } + public function getRequestAsArray() + { + $array = $this->toArray(); + unset($array['process_version']['svg']); + return $array; + } + /** * Get the case title from the process * @@ -1049,10 +1056,6 @@ public function isSystem() public function getProcessVersionAlternativeAttribute(): string | null { - if (class_exists('ProcessMaker\Package\PackageABTesting\Models\Alternative')) { - return $this->processVersion?->alternative; - } - - return null; + return $this->processVersion?->alternative ?? 'A'; } } diff --git a/ProcessMaker/Models/ProcessVersion.php b/ProcessMaker/Models/ProcessVersion.php index b78283e37d..eafa4531d4 100644 --- a/ProcessMaker/Models/ProcessVersion.php +++ b/ProcessMaker/Models/ProcessVersion.php @@ -60,6 +60,7 @@ class ProcessVersion extends ProcessMakerModel implements ProcessModelInterface */ protected $hidden = [ 'bpmn', + 'svg', ]; protected static function boot() diff --git a/ProcessMaker/Models/Screen.php b/ProcessMaker/Models/Screen.php index 61afd89519..57e59a7fba 100644 --- a/ProcessMaker/Models/Screen.php +++ b/ProcessMaker/Models/Screen.php @@ -105,28 +105,6 @@ class Screen extends ProcessMakerModel implements ScreenInterface 'updated_at', ]; - /** - * Table columns. - * - * @var array - */ - protected $columns = [ - 'id', - 'uuid', - 'screen_category_id', - 'title', - 'description', - 'type', - 'config', - 'computed', - 'custom_css', - 'created_at', - 'updated_at', - 'status', - 'key', - 'watchers', - ]; - /** * Validation rules * @@ -185,16 +163,6 @@ public function projectAssets() ->wherePivot('asset_type', static::class)->withTimestamps(); } - public function scopeExclude($query, $value = []) - { - $columns = array_diff($this->columns, (array) $value); - $columns = array_map(function ($column) { - return $this->table . '.' . $column; - }, $columns); - - return $query->select($columns); - } - /** * Set multiple|single categories to the screen * diff --git a/ProcessMaker/Models/Setting.php b/ProcessMaker/Models/Setting.php index 3f36942a3d..e6167a2517 100644 --- a/ProcessMaker/Models/Setting.php +++ b/ProcessMaker/Models/Setting.php @@ -430,6 +430,7 @@ function ($settings) use ($id) { public static function updateAllSettingsGroupId() { Setting::whereNull('group_id')->chunk(100, function ($settings) { + $defaultId = SettingsMenus::EMAIL_MENU_GROUP; foreach ($settings as $setting) { // Define the value of 'menu_group' based on 'group' switch ($setting->group) { @@ -466,7 +467,11 @@ public static function updateAllSettingsGroupId() $id = null; break; default: // The default value - $id = SettingsMenus::getId(SettingsMenus::EMAIL_MENU_GROUP); + if (preg_match('/^Email Server/', $setting->group)) { + $id = SettingsMenus::getId(SettingsMenus::EMAIL_MENU_GROUP); + } else { + $id = SettingsMenus::getId($defaultId); + } break; } if ($id !== null) { diff --git a/ProcessMaker/Models/User.php b/ProcessMaker/Models/User.php index 670f9834fb..cf95779fe6 100644 --- a/ProcessMaker/Models/User.php +++ b/ProcessMaker/Models/User.php @@ -150,7 +150,19 @@ class User extends Authenticatable implements HasMedia public static function boot() { parent::boot(); + + static::deleting(function ($user) { + $user->status = 'INACTIVE'; + $user->save(); + }); + + static::restoring(function ($user) { + $user->status = 'ACTIVE'; + $user->save(); + }); + static::deleted(function ($user) { + $user->status = 'INACTIVE'; $user->removeFromGroups(); }); } diff --git a/ProcessMaker/Models/WizardTemplate.php b/ProcessMaker/Models/WizardTemplate.php index a7308bf5b7..0e09ff546e 100644 --- a/ProcessMaker/Models/WizardTemplate.php +++ b/ProcessMaker/Models/WizardTemplate.php @@ -76,10 +76,12 @@ public function getTemplateMediaAttribute() }); $iconMedia = $this->getMedia($mediaCollectionName, ['media_type' => 'icon'])->first(); $cardBackgroundMedia = $this->getMedia($mediaCollectionName, ['media_type' => 'cardBackground'])->first(); + $listIconMedia = $this->getMedia($mediaCollectionName, ['media_type' => 'listIcon'])->first(); return [ 'icon' => !is_null($iconMedia) ? $iconMedia->getFullUrl() : '', 'cardBackground' => !is_null($cardBackgroundMedia) ? $cardBackgroundMedia->getFullUrl() : '', + 'listIcon' => !is_null($listIconMedia) ? $listIconMedia->getFullUrl() : '', 'slides' => $slideUrls, ]; } diff --git a/ProcessMaker/Observers/ProcessRequestObserver.php b/ProcessMaker/Observers/ProcessRequestObserver.php index 684b793384..1ba10c2392 100644 --- a/ProcessMaker/Observers/ProcessRequestObserver.php +++ b/ProcessMaker/Observers/ProcessRequestObserver.php @@ -51,7 +51,7 @@ public function saved(ProcessRequest $request) if ($request->status === 'ERROR') { $errors = $request->getAttribute('errors') ?? []; foreach ($errors as $error) { - event(new RequestError($request, $error['message'])); + RequestError::dispatchIfNotRateLimited($request, $error['message']); } } if ($request->status === 'COMPLETED') { diff --git a/ProcessMaker/Providers/ProcessMakerServiceProvider.php b/ProcessMaker/Providers/ProcessMakerServiceProvider.php index 8507a425d4..d90d801efb 100644 --- a/ProcessMaker/Providers/ProcessMakerServiceProvider.php +++ b/ProcessMaker/Providers/ProcessMakerServiceProvider.php @@ -14,6 +14,8 @@ use Laravel\Horizon\Horizon; use Laravel\Passport\Passport; use Lavary\Menu\Menu; +use Illuminate\Database\Console\Migrations\MigrateCommand; +use ProcessMaker\Console\Migration\ExtendedMigrateCommand; use ProcessMaker\Events\ActivityAssigned; use ProcessMaker\Events\ScreenBuilderStarting; use ProcessMaker\Helpers\PmHash; @@ -149,6 +151,13 @@ public function register(): void $this->app->singleton(PackageManifest::class, fn () => new LicensedPackageManifest( new Filesystem, $this->app->basePath(), $this->app->getCachedPackagesPath() )); + + $this->app->extend(MigrateCommand::class, function () { + return new ExtendedMigrateCommand( + app('migrator'), + app('events') + ); + }); } /** diff --git a/ProcessMaker/Traits/TaskControllerIndexMethods.php b/ProcessMaker/Traits/TaskControllerIndexMethods.php index 3a49a4e105..e4d171cc45 100644 --- a/ProcessMaker/Traits/TaskControllerIndexMethods.php +++ b/ProcessMaker/Traits/TaskControllerIndexMethods.php @@ -7,7 +7,6 @@ use Illuminate\Support\Arr; use Illuminate\Support\Str; use ProcessMaker\Filters\Filter; -use ProcessMaker\Http\Resources\Task as Resource; use ProcessMaker\Models\ProcessRequest; use ProcessMaker\Models\ProcessRequestToken; use ProcessMaker\Models\User; @@ -17,8 +16,21 @@ trait TaskControllerIndexMethods { private function indexBaseQuery($request) { - $query = ProcessRequestToken::with(['processRequest', 'user', 'draft']); - $query->select('process_request_tokens.*'); + $query = ProcessRequestToken::exclude(['data'])->with([ + 'processRequest' => fn($q) => $q->exclude(['data']), + // review if bpmn is reuiqred here process + 'process' => fn($q) => $q->exclude(['svg', 'warnings']), + // review if bpmn is reuiqred here processRequest.process + 'processRequest.process' => fn($q) => $q->exclude(['svg', 'warnings']), + // The following lines use to much memory but reduce the number of queries + // bpmn is required here in processRequest.processVersion + // 'processRequest.processVersion' => fn($q) => $q->exclude(['svg', 'warnings']), + // review if bpmn is reuiqred here processRequest.processVersion.process + // 'processRequest.processVersion.process' => fn($q) => $q->exclude(['svg', 'warnings']), + 'user', + 'draft' + ]); + $include = $request->input('include') ? explode(',', $request->input('include')) : []; foreach (['data'] as $key) { diff --git a/ProcessMaker/Traits/TaskResourceIncludes.php b/ProcessMaker/Traits/TaskResourceIncludes.php index 8b8db4a0f7..6bde425edc 100644 --- a/ProcessMaker/Traits/TaskResourceIncludes.php +++ b/ProcessMaker/Traits/TaskResourceIncludes.php @@ -70,7 +70,7 @@ private function includeScreen($request) if ($array['screen']) { // Apply translations to screen - $processTranslation = new ProcessTranslation($this->process); + $processTranslation = new ProcessTranslation($this->processRequest->process); $array['screen']['config'] = $processTranslation->applyTranslations($array['screen']); // Apply translations to nested screens diff --git a/composer.json b/composer.json index 9da61f9627..15b5c329ed 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "processmaker/processmaker", - "version": "4.10.2", + "version": "4.10.3+nightly-20240724", "description": "BPM PHP Software", "keywords": [ "php bpm processmaker" @@ -103,7 +103,7 @@ "Gmail" ], "processmaker": { - "build": "663597d7", + "build": "4ee84764", "custom": { "package-ellucian-ethos": "1.16.0", "package-plaid": "1.6.0", @@ -135,25 +135,25 @@ }, "enterprise": { "connector-docusign": "1.9.0", - "connector-idp": "1.9.4", - "connector-pdf-print": "1.16.1", - "connector-send-email": "1.26.3", + "connector-idp": "1.9.5", + "connector-pdf-print": "1.16.2", + "connector-send-email": "1.26.5", "connector-slack": "1.8.0", "docker-executor-cdata": "1.4.1", "docker-executor-node-ssr": "1.5.0", - "package-ab-testing": "1.1.0", + "package-ab-testing": "1.1.1", "package-actions-by-email": "1.17.0", "package-advanced-user-manager": "1.11.0", "package-ai": "1.8.1", "package-analytics-reporting": "1.7.1", - "package-auth": "1.19.1", + "package-auth": "1.19.2", "package-cdata": "1.4.5", - "package-collections": "2.17.0", + "package-collections": "2.17.1", "package-comments": "1.13.0", "package-conversational-forms": "1.8.2", - "package-data-sources": "1.27.1", + "package-data-sources": "1.27.2", "package-decision-engine": "1.9.16", - "package-dynamic-ui": "1.20.1", + "package-dynamic-ui": "1.20.2", "package-files": "1.16.0", "package-googleplaces": "1.11.0", "package-photo-video": "1.5.1", @@ -161,8 +161,8 @@ "package-process-documenter": "1.9.0", "package-process-optimization": "1.10.0", "package-product-analytics": "1.5.8", - "package-projects": "1.4.0", - "package-savedsearch": "1.34.1", + "package-projects": "1.4.1", + "package-savedsearch": "1.34.3", "package-sentry": "1.8.0", "package-signature": "1.12.0", "package-testing": "1.3.1", @@ -180,7 +180,7 @@ "microservices": { "pmai": "fall-2023" }, - "release": "Spring 2024" + "release": "Spring 2024 Pre-Release" } }, "scripts": { diff --git a/composer.lock b/composer.lock index b6eb61e441..bbb7df4469 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c9e9b1d2cef311e6d2ff69e6caa5b42c", + "content-hash": "0607c18488fcf4fe7c54a6205ac88c91", "packages": [ { "name": "aws/aws-crt-php", diff --git a/config/app.php b/config/app.php index 419e440472..321e827ed6 100644 --- a/config/app.php +++ b/config/app.php @@ -234,4 +234,8 @@ 'task_drafts_enabled' => env('TASK_DRAFTS_ENABLED', true), 'force_https' => env('FORCE_HTTPS', true), + + // Process Request security log rate limit: 1 per day (86400 seconds) + 'process_request_errors_rate_limit' => env('PROCESS_REQUEST_ERRORS_RATE_LIMIT', 1), + 'process_request_errors_rate_limit_duration' => env('PROCESS_REQUEST_ERRORS_RATE_LIMIT_DURATION', 86400), ]; diff --git a/package-lock.json b/package-lock.json index 4a7f409862..0c0652b151 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@processmaker/processmaker", - "version": "4.10.2", + "version": "4.10.3+nightly-20240724", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@processmaker/processmaker", - "version": "4.10.2", + "version": "4.10.3+nightly-20240724", "hasInstallScript": true, "license": "ISC", "dependencies": { @@ -20,10 +20,10 @@ "@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/vue-fontawesome": "^0.1.9", "@panter/vue-i18next": "^0.15.2", - "@processmaker/modeler": "1.48.0", + "@processmaker/modeler": "1.48.1", "@processmaker/processmaker-bpmn-moddle": "0.14.1", - "@processmaker/screen-builder": "2.87.1", - "@processmaker/vue-form-elements": "0.53.0", + "@processmaker/screen-builder": "2.87.2", + "@processmaker/vue-form-elements": "0.53.1", "@processmaker/vue-multiselect": "2.3.0", "@tinymce/tinymce-vue": "2.0.0", "autoprefixer": "10.4.5", @@ -2970,9 +2970,9 @@ } }, "node_modules/@processmaker/modeler": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/@processmaker/modeler/-/modeler-1.48.0.tgz", - "integrity": "sha512-uCo55NyznspN3TPra3MAo4r1wegal0PNDMYiPirmFyQhInm0eYLOsFkzqB8EhjdWx7At8zr8DMnwiWQaq9CpyQ==", + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@processmaker/modeler/-/modeler-1.48.1.tgz", + "integrity": "sha512-bASIe/wSKqwB/VohJBMnrkY3WCDgeod6eGcEzxxCIUP7wMZH0EKV6E/sITrtw9g03jetWKCQEtVA5jF3xRkucQ==", "dependencies": { "@babel/plugin-proposal-private-methods": "^7.12.1", "@fortawesome/fontawesome-free": "^5.11.2", @@ -2980,8 +2980,8 @@ "@fortawesome/free-brands-svg-icons": "^6.4.2", "@fortawesome/free-solid-svg-icons": "^5.11.2", "@fortawesome/vue-fontawesome": "^0.1.8", - "@processmaker/screen-builder": "2.87.0", - "@processmaker/vue-form-elements": "0.53.0", + "@processmaker/screen-builder": "2.87.2", + "@processmaker/vue-form-elements": "0.53.1", "@processmaker/vue-multiselect": "2.3.0", "bootstrap": "^4.3.1", "bootstrap-vue": "^2.0.4", @@ -3052,52 +3052,6 @@ "node": ">=6" } }, - "node_modules/@processmaker/modeler/node_modules/@processmaker/screen-builder": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/@processmaker/screen-builder/-/screen-builder-2.87.0.tgz", - "integrity": "sha512-kq9/KMr6c4Q8mwUSJCtRjYr2cwgGoYmHMDU5I+2BSpQ7R7vu0KFduNFcu2l5EoMb3/qZX9TQxfVdSE+et+3Gxw==", - "dependencies": { - "@chantouchsek/validatorjs": "1.2.3", - "@storybook/addon-docs": "^7.6.13", - "axios-extensions": "^3.1.6", - "lodash": "^4.17.21", - "lru-cache": "^10.0.1", - "moment": "^2.30.1", - "moment-timezone": "^0.5.45", - "monaco-editor": "^0.34.0", - "scrollparent": "^2.0.1", - "vue-loader": "^15.9.2", - "vue-monaco": "^0.3.2", - "vue-simple-uploader": "^0.7.4", - "vue-the-mask": "^0.11.1", - "vuelidate": "^0.7.5" - }, - "engines": { - "node": ">=16", - "npm": ">=8" - }, - "peerDependencies": { - "@panter/vue-i18next": "^0.15.0", - "@processmaker/vue-form-elements": "0.53.0", - "i18next": "^15.0.8", - "vue": "^2.6.12", - "vuex": "^3.1.1" - } - }, - "node_modules/@processmaker/modeler/node_modules/@processmaker/screen-builder/node_modules/vue-monaco": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/vue-monaco/-/vue-monaco-0.3.2.tgz", - "integrity": "sha512-GWdtTuxUxse+tqs7B9zH3CosCZlpXNae1G7dlI1lmUrEDG+vtoBtulUBjhLseHZIwsuthebcRNgkVSdDMa8Tcw==", - "dependencies": { - "monaco-editor": "^0.17.0", - "nano-assign": "^1.0.0" - } - }, - "node_modules/@processmaker/modeler/node_modules/@processmaker/screen-builder/node_modules/vue-monaco/node_modules/monaco-editor": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.17.1.tgz", - "integrity": "sha512-JAc0mtW7NeO+0SwPRcdkfDbWLgkqL9WfP1NbpP9wNASsW6oWqgZqNIWt4teymGjZIXTElx3dnQmUYHmVrJ7HxA==" - }, "node_modules/@processmaker/modeler/node_modules/engine.io-client": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz", @@ -3118,14 +3072,6 @@ "node": ">=10.0.0" } }, - "node_modules/@processmaker/modeler/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "engines": { - "node": "14 || >=16.14" - } - }, "node_modules/@processmaker/modeler/node_modules/socket.io-client": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", @@ -3189,9 +3135,9 @@ } }, "node_modules/@processmaker/screen-builder": { - "version": "2.87.1", - "resolved": "https://registry.npmjs.org/@processmaker/screen-builder/-/screen-builder-2.87.1.tgz", - "integrity": "sha512-9V5B2zrbBod+dxOcVFL8uDx1MQdI8xdhPRyRHDfLE1EYXBVf/YjK6l8tGUZh+t2WUFscrbKFtbvbZoDZ2FQ9uw==", + "version": "2.87.2", + "resolved": "https://registry.npmjs.org/@processmaker/screen-builder/-/screen-builder-2.87.2.tgz", + "integrity": "sha512-BiWeoR6cqhJAeGaIGy/zX6Am4zTuKjGfZhd32OeIA9gnjqUOmyy/3RlizE9N+WZENwodbS8WCQ51FmnhEYF+eQ==", "dependencies": { "@chantouchsek/validatorjs": "1.2.3", "@storybook/addon-docs": "^7.6.13", @@ -3214,7 +3160,7 @@ }, "peerDependencies": { "@panter/vue-i18next": "^0.15.0", - "@processmaker/vue-form-elements": "0.53.0", + "@processmaker/vue-form-elements": "0.53.1", "i18next": "^15.0.8", "vue": "^2.6.12", "vuex": "^3.1.1" @@ -3243,9 +3189,9 @@ "integrity": "sha512-JAc0mtW7NeO+0SwPRcdkfDbWLgkqL9WfP1NbpP9wNASsW6oWqgZqNIWt4teymGjZIXTElx3dnQmUYHmVrJ7HxA==" }, "node_modules/@processmaker/vue-form-elements": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@processmaker/vue-form-elements/-/vue-form-elements-0.53.0.tgz", - "integrity": "sha512-Dw3Dt/c1MajHYmnloX6AdUHS09hAjaBiz6qflexOX5uFmDfr/3lx+uaupQcIWNUVXP5cbUXUzdcNtACdJTyxEw==", + "version": "0.53.1", + "resolved": "https://registry.npmjs.org/@processmaker/vue-form-elements/-/vue-form-elements-0.53.1.tgz", + "integrity": "sha512-uT1m1QcOlm4/XQv6vKquinFXgavdA/uycBr9wzMhwxLMaH2iefqMS3AqGipmIBXLIcQO6y85TiR+BIw4xczwzw==", "dependencies": { "@chantouchsek/validatorjs": "1.2.3", "@tinymce/tinymce-vue": "2.0.0", @@ -20960,9 +20906,9 @@ "optional": true }, "@processmaker/modeler": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/@processmaker/modeler/-/modeler-1.48.0.tgz", - "integrity": "sha512-uCo55NyznspN3TPra3MAo4r1wegal0PNDMYiPirmFyQhInm0eYLOsFkzqB8EhjdWx7At8zr8DMnwiWQaq9CpyQ==", + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@processmaker/modeler/-/modeler-1.48.1.tgz", + "integrity": "sha512-bASIe/wSKqwB/VohJBMnrkY3WCDgeod6eGcEzxxCIUP7wMZH0EKV6E/sITrtw9g03jetWKCQEtVA5jF3xRkucQ==", "requires": { "@babel/plugin-proposal-private-methods": "^7.12.1", "@fortawesome/fontawesome-free": "^5.11.2", @@ -20970,8 +20916,8 @@ "@fortawesome/free-brands-svg-icons": "^6.4.2", "@fortawesome/free-solid-svg-icons": "^5.11.2", "@fortawesome/vue-fontawesome": "^0.1.8", - "@processmaker/screen-builder": "2.87.0", - "@processmaker/vue-form-elements": "0.53.0", + "@processmaker/screen-builder": "2.87.2", + "@processmaker/vue-form-elements": "0.53.1", "@processmaker/vue-multiselect": "2.3.0", "bootstrap": "^4.3.1", "bootstrap-vue": "^2.0.4", @@ -21016,45 +20962,6 @@ "@fortawesome/fontawesome-common-types": "6.4.2" } }, - "@processmaker/screen-builder": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/@processmaker/screen-builder/-/screen-builder-2.87.0.tgz", - "integrity": "sha512-kq9/KMr6c4Q8mwUSJCtRjYr2cwgGoYmHMDU5I+2BSpQ7R7vu0KFduNFcu2l5EoMb3/qZX9TQxfVdSE+et+3Gxw==", - "requires": { - "@chantouchsek/validatorjs": "1.2.3", - "@storybook/addon-docs": "^7.6.13", - "axios-extensions": "^3.1.6", - "lodash": "^4.17.21", - "lru-cache": "^10.0.1", - "moment": "^2.30.1", - "moment-timezone": "^0.5.45", - "monaco-editor": "^0.34.0", - "scrollparent": "^2.0.1", - "vue-loader": "^15.9.2", - "vue-monaco": "^0.3.2", - "vue-simple-uploader": "^0.7.4", - "vue-the-mask": "^0.11.1", - "vuelidate": "^0.7.5" - }, - "dependencies": { - "vue-monaco": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/vue-monaco/-/vue-monaco-0.3.2.tgz", - "integrity": "sha512-GWdtTuxUxse+tqs7B9zH3CosCZlpXNae1G7dlI1lmUrEDG+vtoBtulUBjhLseHZIwsuthebcRNgkVSdDMa8Tcw==", - "requires": { - "monaco-editor": "^0.17.0", - "nano-assign": "^1.0.0" - }, - "dependencies": { - "monaco-editor": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.17.1.tgz", - "integrity": "sha512-JAc0mtW7NeO+0SwPRcdkfDbWLgkqL9WfP1NbpP9wNASsW6oWqgZqNIWt4teymGjZIXTElx3dnQmUYHmVrJ7HxA==" - } - } - } - } - }, "engine.io-client": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz", @@ -21072,11 +20979,6 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==" }, - "lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==" - }, "socket.io-client": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", @@ -21119,9 +21021,9 @@ } }, "@processmaker/screen-builder": { - "version": "2.87.1", - "resolved": "https://registry.npmjs.org/@processmaker/screen-builder/-/screen-builder-2.87.1.tgz", - "integrity": "sha512-9V5B2zrbBod+dxOcVFL8uDx1MQdI8xdhPRyRHDfLE1EYXBVf/YjK6l8tGUZh+t2WUFscrbKFtbvbZoDZ2FQ9uw==", + "version": "2.87.2", + "resolved": "https://registry.npmjs.org/@processmaker/screen-builder/-/screen-builder-2.87.2.tgz", + "integrity": "sha512-BiWeoR6cqhJAeGaIGy/zX6Am4zTuKjGfZhd32OeIA9gnjqUOmyy/3RlizE9N+WZENwodbS8WCQ51FmnhEYF+eQ==", "requires": { "@chantouchsek/validatorjs": "1.2.3", "@storybook/addon-docs": "^7.6.13", @@ -21163,9 +21065,9 @@ } }, "@processmaker/vue-form-elements": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@processmaker/vue-form-elements/-/vue-form-elements-0.53.0.tgz", - "integrity": "sha512-Dw3Dt/c1MajHYmnloX6AdUHS09hAjaBiz6qflexOX5uFmDfr/3lx+uaupQcIWNUVXP5cbUXUzdcNtACdJTyxEw==", + "version": "0.53.1", + "resolved": "https://registry.npmjs.org/@processmaker/vue-form-elements/-/vue-form-elements-0.53.1.tgz", + "integrity": "sha512-uT1m1QcOlm4/XQv6vKquinFXgavdA/uycBr9wzMhwxLMaH2iefqMS3AqGipmIBXLIcQO6y85TiR+BIw4xczwzw==", "requires": { "@chantouchsek/validatorjs": "1.2.3", "@tinymce/tinymce-vue": "2.0.0", diff --git a/package.json b/package.json index e83c766702..17f8f13b77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@processmaker/processmaker", - "version": "4.10.2", + "version": "4.10.3+nightly-20240724", "description": "ProcessMaker 4", "author": "DevOps ", "license": "ISC", @@ -51,10 +51,10 @@ "@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/vue-fontawesome": "^0.1.9", "@panter/vue-i18next": "^0.15.2", - "@processmaker/modeler": "1.48.0", + "@processmaker/modeler": "1.48.1", "@processmaker/processmaker-bpmn-moddle": "0.14.1", - "@processmaker/screen-builder": "2.87.1", - "@processmaker/vue-form-elements": "0.53.0", + "@processmaker/screen-builder": "2.87.2", + "@processmaker/vue-form-elements": "0.53.1", "@processmaker/vue-multiselect": "2.3.0", "@tinymce/tinymce-vue": "2.0.0", "autoprefixer": "10.4.5", diff --git a/resources/js/components/common/mixins/datatable.js b/resources/js/components/common/mixins/datatable.js index 257b8bd61a..85271c6259 100644 --- a/resources/js/components/common/mixins/datatable.js +++ b/resources/js/components/common/mixins/datatable.js @@ -109,6 +109,9 @@ export default { // Some controllers return each row as a json object to preserve integer keys (ie saved search) jsonRows(rows) { if (rows.length === 0 || !(_.has(_.head(rows), "_json"))) { + if (!Array.isArray(rows) && typeof rows === "object") { + return Object.values(rows); + } return rows; } return rows.map((row) => JSON.parse(row._json)); diff --git a/resources/js/components/shared/Modal.vue b/resources/js/components/shared/Modal.vue index 10a3d369d7..fecf6f7a4b 100644 --- a/resources/js/components/shared/Modal.vue +++ b/resources/js/components/shared/Modal.vue @@ -13,6 +13,7 @@ :hide-header="hideHeader" :header-class="headerClass" :footer-class="footerClass" + :content-class="contentClass" :size="size" :ok-only="okOnly" no-close-on-backdrop @@ -132,7 +133,8 @@ "requiredInFooter", "titleIcon", "headerClass", - "footerClass" + "footerClass", + "contentClass" ], methods: { onEvent(name, event) { diff --git a/resources/js/components/shared/PmqlInput.vue b/resources/js/components/shared/PmqlInput.vue index 0c0c9c01e8..bda1093c1d 100755 --- a/resources/js/components/shared/PmqlInput.vue +++ b/resources/js/components/shared/PmqlInput.vue @@ -243,6 +243,8 @@ export default { promptTokens: 0, totalTokens: 0, }, + promptSessionId: "", + currentNonce: "", get, }; }, @@ -312,6 +314,9 @@ export default { this.filtersPmql = this.filtersValue; this.inputAriaLabel = this.ariaLabel; + this.promptSessionId = localStorage.promptSessionId; + this.currentNonce = localStorage.currentNonce; + this.$root.$on("bv::collapse::state", (collapseId, isJustShown) => { this.query = this.value; this.pmql = this.value; @@ -325,6 +330,48 @@ export default { }, methods: { + getNonce() { + const max = 999999999999999; + const nonce = Math.floor(Math.random() * max); + this.currentNonce = nonce; + localStorage.currentNonce = this.currentNonce; + }, + getPromptSession() { + const url = "/package-ai/getPromptSessionHistory"; + + let params = { + server: window.location.host, + }; + + if (this.promptSessionId?.startsWith("ss")) { + this.promptSessionId = ""; + } + + if ( + this.promptSessionId + && this.promptSessionId !== null + && this.promptSessionId !== "" + ) { + params = { + promptSessionId: this.promptSessionId, + }; + } + + ProcessMaker.apiClient + .post(url, params) + .then((response) => { + this.promptSessionId = response.data.promptSessionId; + localStorage.promptSessionId = response.data.promptSessionId; + this.runNLQToPMQL(); + }) + .catch((error) => { + if (error.response.status === 404) { + localStorage.promptSessionId = ""; + this.promptSessionId = ""; + this.getPromptSession(); + } + }); + }, onFiltersPmqlChange(value) { this.filtersPmql = value[0]; this.selectedFilters = value[1]; @@ -381,7 +428,7 @@ export default { this.$emit("submit", this.query); this.$emit("input", this.query); } else if (this.aiEnabledLocal) { - this.runNLQToPMQL(); + this.getPromptSession(); } else if (!this.query.isPMQL() && !this.aiEnabledLocal) { const fullTextSearch = `(fulltext LIKE "%${this.query}%")`; this.pmql = fullTextSearch; @@ -395,10 +442,13 @@ export default { this.runSearch(); }, runNLQToPMQL() { + this.getNonce(); const params = { - question: this.query, + search: this.query, type: this.searchType, classifySearch: false, + promptSessionId: this.promptSessionId, + nonce: this.currentNonce, }; this.aiLoading = true; @@ -406,8 +456,8 @@ export default { ProcessMaker.apiClient .post("/package-ai/naturalLanguageToPmql", params) .then((response) => { - this.pmql = response.data.result; - this.usage = response.data.usage; + this.pmql = response.data.result[0].result.pmql; + this.usage = response.data.result[0].usage; this.$emit("submit", this.pmql); this.$emit("input", this.pmql); this.aiLoading = false; diff --git a/resources/js/components/templates/WizardHelperProcessModal.vue b/resources/js/components/templates/WizardHelperProcessModal.vue index 1402d351f8..f1c1468b1c 100644 --- a/resources/js/components/templates/WizardHelperProcessModal.vue +++ b/resources/js/components/templates/WizardHelperProcessModal.vue @@ -48,7 +48,7 @@ export default { font-weight: 400; } .wizard-details-description { - font-size: 16px; + font-size: 20px; } .wizard-details-headline { diff --git a/resources/js/components/templates/WizardTemplateDetails.vue b/resources/js/components/templates/WizardTemplateDetails.vue index 39056c3327..e84ee5175d 100644 --- a/resources/js/components/templates/WizardTemplateDetails.vue +++ b/resources/js/components/templates/WizardTemplateDetails.vue @@ -2,28 +2,33 @@
- + + + + + +
- - - - -
{{ template.name | str_limit(30) }}
-
-
-

{{ templateDetails['modal-excerpt'] | str_limit(150) }}

-

{{ templateDetails['modal-description'] | str_limit(150) }}

+
+

{{ templateDetails['modal-title']| str_limit(30) }}

+

{{ templateDetails['modal-excerpt'] | str_limit(150) }}

+
+ + + + {{ item | str_limit(150) }} +
+
- - - - - 1 ? 3000 : 0; } @@ -116,20 +119,39 @@ export default { font-weight: 400; } .wizard-details-description { - font-size: 16px; + font-size: 18px; + line-height: 3px; + font-weight: lighter; +} + +.wizard-details-description .template-list-item{ + line-height:1.5rem; } .wizard-details-headline { - font-size: 26px; + font-size: 20px; + margin-bottom: 22px; + font-weight:lighter; } -.wizard-details-button{ +.wizard-details-button { border-radius: 11px; - border: none; + border: none!important; background-color: #1572C2; color: #FFFFFF; display: inline-flex; padding: 7px 16px; align-items: center; } + +.wizard-details-button:focus { + border:none; + outline: none; +} + +.template-divider { + background-color: #0081D8; + margin-top: 2rem; + margin-bottom: 2rem; +} diff --git a/resources/js/requests/components/RequestScreens.vue b/resources/js/requests/components/RequestScreens.vue index 711d90e15f..6fb71f9fad 100644 --- a/resources/js/requests/components/RequestScreens.vue +++ b/resources/js/requests/components/RequestScreens.vue @@ -1,6 +1,17 @@