data['credentials'] ?? null; $credits = null; $error = null; $access = false; if ($c) { try { $c = picsart_runtime_token($store); $credits = picsart_runtime_balance($store); picsart_runtime_drive_items($store, null, 'image'); $access = true; } catch (Picsart_Runtime_Error $e) { $error = $e->getMessage(); $c = $store->data['credentials'] ?? null; } } $subject = $c['subject'] ?? null; $scopes = preg_split('/\s+/', $c['scope'] ?? ''); $granted = !array_diff(['workflows.execute','drive','credits.balance'], $scopes); $jobs = array_values(array_filter($store->data['jobs'], static fn($j)=>$subject && $j['subject'] === $subject)); $spent = array_column(array_column($jobs, 'quote'), 'id'); $quotes = array_filter($store->data['quotes'], static fn($q)=>$subject && $q['subject'] === $subject && $q['expiresAt'] > time() * 1000 + 5000 && !in_array($q['id'], $spent, true)); $auth = ['authenticated'=>(bool)$c,'subject'=>$subject,'name'=>$c['name'] ?? null,'canGenerate'=>$access && $granted && current_user_can('picsart_generate'),'requiresReconnect'=>(bool)$c && !$granted]; if (!empty($store->data['device']) && $store->data['device']['expiresAt'] > time()) $auth['pendingDevice'] = picsart_runtime_pending($store->data['device']); $tool = ['available'=>true,'mode'=>'account-oauth','reason'=>__('Request an exact quote and approve it to run this image tool.', 'picsart-commerce')]; return ['auth'=>$auth,'wordpress'=>['configured'=>true,'connected'=>true],'credits'=>$credits,'creditError'=>$error,'generationError'=>$error,'reserved'=>array_sum(array_column($jobs,'reservation')),'jobs'=>array_map('picsart_runtime_public_job',$jobs),'sources'=>array_values(array_filter($store->data['sources'],static fn($s)=>empty($s['subject']) || $s['subject'] === $subject)),'quotes'=>array_values(array_map('picsart_runtime_public_quote',$quotes)),'templates'=>picsart_runtime_manifest()['templates'],'csrf'=>'','setup'=>[],'capabilities'=>['removeBackground'=>$tool,'upscale'=>$tool,'legacyEditor'=>['available'=>false]],'savedTemplates'=>array_values(array_filter($store->data['savedTemplates'],static fn($t)=>$t['subject'] === $subject)),'notificationPreferences'=>picsart_runtime_preferences($store,$subject)]; } function picsart_runtime_preferences($store, $subject) { $defaults = array_fill_keys(['suggestions','setup','started','delayed','ready','exports','attention','support'],true); return ($store->data['preferences'][$subject ?? ''] ?? []) + $defaults; } function picsart_runtime_job_action($store, $id, $action, $body) { $i = picsart_runtime_job_index($store,$id); $job =& $store->data['jobs'][$i]; $subject = picsart_runtime_token($store)['subject']; if ($action === 'video') { picsart_runtime_require(!empty($job['drive']), 'OUTPUT', __('Wait for the retained result.', 'picsart-commerce')); return ['url'=>$job['drive']['url'],'downloadUrl'=>$job['drive']['url']]; } if ($action === 'review') { picsart_runtime_require($job['status'] === 'REVIEW' && !empty($job['drive']) && is_bool($body['accept'] ?? null), 'REVIEW', __('Review the completed result first.', 'picsart-commerce')); $job['status'] = $body['accept'] ? 'ACCEPTED' : 'REJECTED'; $job['reviewedAt'] = time() * 1000; $job['reviewedBy'] = $subject; if (!$body['accept']) $job['reviewReason'] = sanitize_text_field(substr($body['reason'] ?? '',0,300)); $store->save(); return ['ok'=>true]; } if ($action === 'archive') { picsart_runtime_require(is_bool($body['archived'] ?? null), 'ARCHIVE', __('Choose whether to archive this result.', 'picsart-commerce')); if ($body['archived']) $job['archivedAt'] = time() * 1000; else unset($job['archivedAt']); $store->save(); return ['ok'=>true]; } if (in_array($action, ['save','finish-photo-save'], true) && !empty($job['mediaId']) && in_array($job['status'], ['SAVED','ATTACHED'], true)) { $attachment = get_post((int)$job['mediaId']); picsart_runtime_require($attachment && $attachment->post_type === 'attachment' && current_user_can('edit_post', $attachment->ID), 'COPY', __('The saved attachment is no longer available.', 'picsart-commerce'), 409); return ['ok'=>true]; } if ($action === 'finish-photo-save' && $job['status'] === 'IMPORT_UNKNOWN') { picsart_runtime_require(!empty($job['reviewedAt']) && ($job['reviewedBy'] ?? '') === $subject && !empty($job['drive']), 'REVIEW', __('Accept the result before copying it to WordPress.', 'picsart-commerce')); // Reuse the exact native import receipt. Completed copies return their ID; // an in-flight/unknown native receipt refuses another download or attachment. $job['status'] = 'ACCEPTED'; $store->save(); unset($job); return picsart_runtime_job_action($store,$id,'save',[]); } if ($action === 'check-result' || $action === 'finish-photo-save') { picsart_runtime_require(in_array($job['status'], ['GENERATING','PERSISTING','UNKNOWN_SUBMISSION','RECONCILIATION_REQUIRED'], true) && empty($job['retentionStarted']) && (!empty($job['upstream']) || !empty($job['outputUrl'])), 'RECOVERY', __('There is no confirmed receipt to check. No new generation was started.', 'picsart-commerce')); $job['status'] = !empty($job['outputUrl']) ? 'PERSISTING' : 'GENERATING'; $job['nextPollAt'] = 0; $store->save(); unset($job); return picsart_runtime_tick($store,$id); } picsart_runtime_require(!empty($job['reviewedAt']) && ($job['reviewedBy'] ?? '') === $subject && in_array($job['status'],['ACCEPTED','SAVED','ATTACHED'],true), 'REVIEW', __('Accept the completed result before using it.', 'picsart-commerce')); if ($action === 'save') { if (!empty($job['mediaId'])) return ['ok'=>true]; $job['status'] = 'IMPORTING'; $store->save(); try { $r = picsart_runtime_platform('import',['url'=>$job['drive']['url'],'options'=>['displayName'=>'Picsart-' . $id]]); $job['mediaId'] = $r['file']['_id']; $job['status'] = 'SAVED'; } catch (Throwable $e) { $job['status'] = 'IMPORT_UNKNOWN'; picsart_runtime_event($job, __('WordPress could not confirm the media copy. Check the save status to try completing this copy.', 'picsart-commerce')); $store->save(); throw $e; } $store->save(); return ['ok'=>true]; } if ($action === 'use-photo') { picsart_runtime_require($job['quote']['kind'] === 'image', 'PHOTO', __('Choose an image.', 'picsart-commerce')); $source = ['id'=>'edit-' . $id,'subject'=>$subject,'url'=>$job['drive']['url'],'name'=>'Edited image ' . substr($id,0,8)] + picsart_runtime_image_dimensions($job['drive']['url']); foreach (['productId','instanceId','productName','sku'] as $key) if (isset($job['quote']['source'][$key])) $source[$key] = $job['quote']['source'][$key]; $store->data['sources'] = array_values(array_filter($store->data['sources'],static fn($s)=>$s['id'] !== $source['id'])); $store->data['sources'][] = $source; $store->save(); return $source; } picsart_runtime_require(!empty($job['mediaId']), 'COPY', __('Copy the result to WordPress first.', 'picsart-commerce')); if ($action === 'blog-draft') return picsart_runtime_platform('blog-draft',$body + ['attachmentId'=>$job['mediaId']]); $source = $job['quote']['source']; picsart_runtime_require(!empty($source['productId']) && ($source['instanceId'] ?? '') === picsart_runtime_instance(), 'PRODUCT', __('This result does not belong to a product on this site.', 'picsart-commerce')); if ($action === 'apply-image') { picsart_runtime_require($job['quote']['kind'] === 'image' && preg_match('/^(?:product:)?(\d+):(\d+)(?::|$)/',$source['id'],$match), 'SOURCE', __('Use the native image editor for this destination.', 'picsart-commerce')); return picsart_runtime_platform('apply-image',['attachmentId'=>$job['mediaId'],'targetId'=>$source['productId'],'targetType'=>$body['targetType'] ?? '', 'expectedPreviousId'=>(int)$match[2]]); } picsart_runtime_require($job['quote']['kind'] === 'video' && in_array($action,['attach','detach'],true), 'ACTION', __('Unsupported result action.', 'picsart-commerce')); $r = picsart_runtime_platform($action,['productId'=>$source['productId'],'mediaId'=>$job['mediaId'],'sku'=>$source['sku'] ?? '']); $job['status'] = $action === 'attach' ? 'ATTACHED' : 'SAVED'; if ($action === 'detach') $job['detachedAt'] = time() * 1000; $store->save(); return $r; } function picsart_runtime_dispatch($store, $route, $request) { $get = $request->get_method() === 'GET'; $body = $request->get_json_params() ?: []; picsart_runtime_require(is_array($body), 'BODY', __('Invalid request body.', 'picsart-commerce')); if ($route === '/auth/reset-unreadable' && !$get) return ['ok'=>true]; if ($route === '/state' && $get) return picsart_runtime_state($store); if (preg_match('#^/auth/device/(start|poll|cancel)$#',$route,$match) && !$get) return picsart_runtime_login($store,$match[1],$body); if ($route === '/auth/disconnect' && !$get) return picsart_runtime_login($store,'disconnect'); if ($route === '/model-catalog' && $get) return ['catalog'=>picsart_runtime_catalog()['catalog']]; if (in_array($route,['/models','/image-models'],true) && $get) return picsart_runtime_manifest()['catalog'][$route === '/models' ? 'video' : 'image']; if (preg_match('#^/models/([^/]+)/schema$#',$route,$match) && $get) return picsart_runtime_manifest()['catalog']['schemas'][$match[1]] ?? new WP_Error('MODEL',__('Choose an available model.','picsart-commerce'),['status'=>404]); if ($route === '/pricing-catalog' && $get) return ['basis'=>'public-estimate','expiresAt'=>(time() + 300) * 1000,'models'=>picsart_runtime_catalog()['prices']]; if ($route === '/image-tools/status' && $get) return ['available'=>true,'mode'=>'account-oauth']; $subject = picsart_runtime_token($store)['subject']; if ($route === '/quote-context' && !$get) return ['source'=>!empty($body['sourceId']) ? picsart_runtime_source($store,$body['sourceId'],true) : null,'subject'=>$subject,'templates'=>picsart_runtime_manifest()['templates'],'fidelity'=>picsart_runtime_manifest()['fidelity']]; if (in_array($route,['/quotes','/image-quotes'],true) && !$get) return picsart_runtime_quote($store,$body,$route === '/image-quotes'); if ($route === '/approvals' && !$get) return picsart_runtime_approve($store,$body); if ($route === '/tick' && !$get) { picsart_runtime_tick($store); return ['jobs'=>array_values(array_map('picsart_runtime_public_job',array_filter($store->data['jobs'],static fn($j)=>$j['subject'] === $subject)))]; } if (preg_match('#^/jobs/([^/]+)/(review|save|attach|video|use-photo|finish-photo-save|check-result|archive|detach|apply-image|blog-draft)$#',$route,$match)) { picsart_runtime_require(($match[2] === 'video') === $get, 'METHOD', __('Use the supported request method.', 'picsart-commerce'),405); return picsart_runtime_job_action($store,$match[1],$match[2],$body); } if ($route === '/products' && $get) return picsart_runtime_platform('products',['cursor'=>$request->get_param('cursor')]); if (preg_match('#^/products/(\d+)/select$#',$route,$match) && !$get) { $product = picsart_runtime_platform('product',['id'=>(int)$match[1]]); foreach ($product['photos'] as $source) if (!array_filter($store->data['sources'],static fn($s)=>$s['id'] === $source['id'] && (empty($s['subject']) || $s['subject'] === $subject))) $store->data['sources'][] = $source; $store->save(); return $product; } if ($route === '/sources' && !$get) { $file = $request->get_file_params()['photo'] ?? null; picsart_runtime_require($file && !$file['error'] && is_uploaded_file($file['tmp_name']) && $file['size'] <= 15 * MB_IN_BYTES, 'PHOTO', __('Choose a photo up to 15 MB.', 'picsart-commerce')); $size = wp_getimagesize($file['tmp_name']); picsart_runtime_require($size && min($size[0],$size[1]) >= 640 && max($size[0],$size[1]) <= 8192,'PHOTO',__('Choose a photo from 640 to 8192 pixels.','picsart-commerce')); $asset = picsart_runtime_upload_bytes($store,file_get_contents($file['tmp_name']),$size['mime'],'WordPress Product Photo ' . wp_generate_uuid4()); $source = ['id'=>wp_generate_uuid4(),'subject'=>$subject,'url'=>$asset['url'],'name'=>sanitize_text_field(substr($file['name'],0,150)),'width'=>$size[0],'height'=>$size[1]]; $store->data['sources'][] = $source; $store->save(); return $source; } if ($route === '/drive/browse' && $get) { $folder = $request->get_param('folderUid'); return ['folders'=>picsart_runtime_drive_items($store,$folder,'folder'),'images'=>picsart_runtime_drive_items($store,$folder,'image'),'videos'=>picsart_runtime_drive_items($store,$folder,'video')]; } if ($route === '/drive/images' && $get) return picsart_runtime_drive_items($store,null,'image'); if ($route === '/drive/media' && $get) return picsart_runtime_drive_media($store,$request->get_param('uid'),$request->get_param('type'),$request->get_param('folderUid')); if (in_array($route,['/drive/images/select','/drive/import'],true) && !$get) { $file = picsart_runtime_drive_media($store,$body['uid'] ?? '',$route === '/drive/import' ? ($body['type'] ?? '') : 'image',$body['folderUid'] ?? null); if ($route === '/drive/import') { $r = picsart_runtime_platform('import',['url'=>$file['url'],'options'=>['displayName'=>$file['name']]]); return ['mediaId'=>$r['file']['_id'],'status'=>'READY']; } $source = ['id'=>'drive:' . $subject . ':' . $file['uid'],'subject'=>$subject,'url'=>$file['url'],'name'=>$file['name']] + picsart_runtime_image_dimensions($file['url']); $store->data['sources'] = array_values(array_filter($store->data['sources'],static fn($s)=>$s['id'] !== $source['id'])); $store->data['sources'][] = $source; $store->save(); return $source; } if ($route === '/notification-preferences' && !$get) { $prefs = picsart_runtime_preferences($store,$subject); picsart_runtime_require(array_key_exists($body['category'] ?? '',$prefs) && is_bool($body['enabled'] ?? null),'PREFERENCES',__('Choose a notification category.','picsart-commerce')); $prefs[$body['category']] = $body['enabled']; $store->data['preferences'][$subject] = $prefs; $store->save(); return $prefs; } if ($route === '/notification-delivery') return picsart_runtime_platform('notification-preferences',['subject'=>$subject] + ($get ? [] : $body)); if ($route === '/model-sync' && !$get) return ['defaultModel'=>$store->data['defaults'][$subject] ?? null]; if ($route === '/model-default' && !$get) { picsart_runtime_require(($body['subject'] ?? '') === $subject && (is_null($body['model'] ?? null) || isset(picsart_runtime_manifest()['workflows'][$body['model']])),'MODEL',__('Choose an available model for this account.','picsart-commerce')); $store->data['defaults'][$subject] = $body['model'] ?? null; $store->save(); return ['defaultModel'=>$body['model'] ?? null]; } if ($route === '/history/export' && $get) return ['version'=>1,'exportedAt'=>gmdate('c'),'jobs'=>array_values(array_map('picsart_runtime_public_job',array_filter(array_merge($store->data['jobs'],$store->data['archives'] ?? []),static fn($j)=>$j['subject'] === $subject)))]; if ($route === '/history/prune' && !$get) { $ids = $body['jobIds'] ?? []; picsart_runtime_require(is_array($ids) && count($ids) > 0 && count($ids) <= 100,'HISTORY',__('Choose finished archives.','picsart-commerce')); foreach ($ids as $id) { $i = picsart_runtime_job_index($store,$id); $j = $store->data['jobs'][$i]; picsart_runtime_require(!empty($j['archivedAt']) && !$j['reservation'] && in_array($j['status'],['ACCEPTED','REJECTED','SAVED','ATTACHED','STOPPED'],true),'HISTORY',__('Only finished archives can leave the workspace.','picsart-commerce')); } foreach ($store->data['jobs'] as $i=>$j) if ($j['subject'] === $subject && in_array($j['id'],$ids,true)) { $store->data['archives'][] = $j; unset($store->data['jobs'][$i]); } $store->data['jobs'] = array_values($store->data['jobs']); $store->save(); return ['ok'=>true,'count'=>count($ids)]; } if ($route === '/saved-templates' && $get) return array_values(array_filter($store->data['savedTemplates'],static fn($t)=>$t['subject'] === $subject)); if ($route === '/saved-templates' && !$get) { $j = $store->data['jobs'][picsart_runtime_job_index($store,$body['jobId'] ?? '')]; picsart_runtime_require($j['quote']['kind'] === 'video' && ($j['reviewedBy'] ?? '') === $subject && in_array($j['status'],['ACCEPTED','SAVED','ATTACHED'],true) && count($store->data['savedTemplates']) < 100,'REVIEW',__('Accept a completed video before saving its template.','picsart-commerce')); $t = ['id'=>wp_generate_uuid4(),'subject'=>$subject,'jobId'=>$j['id'],'name'=>sanitize_text_field(substr($body['name'] ?? '',0,80)),'model'=>$j['quote']['model'],'promptTemplate'=>$j['quote']['promptTemplate'],'loop'=>$j['quote']['loop']] + $j['quote']['output']; $store->data['savedTemplates'][] = $t; $store->save(); return $t; } if (preg_match('#^/saved-templates/([^/]+)/delete$#',$route,$match) && !$get) { $store->data['savedTemplates'] = array_values(array_filter($store->data['savedTemplates'],static fn($t)=>$t['subject'] !== $subject || $t['id'] !== $match[1])); $store->save(); return ['ok'=>true]; } throw new Picsart_Runtime_Error('NOT_FOUND',esc_html__('This Picsart action is unavailable.','picsart-commerce'),404); } function picsart_runtime_request(WP_REST_Request $request) { $store = null; try { picsart_runtime_require(current_user_can('upload_files'),'PERMISSION',__('Media permission required.','picsart-commerce'),403); $prefs = get_user_meta(get_current_user_id(),'_picsart_preferences',true); picsart_runtime_require(!empty($prefs['consent']),'CONSENT',__('Review Picsart service data sharing before connecting or using cloud tools.','picsart-commerce'),403); picsart_runtime_require(strlen($request->get_body()) <= 16 * MB_IN_BYTES,'SIZE',__('Request too large.','picsart-commerce'),413); $route = $request->get_param('route') ?? '/state'; picsart_runtime_require(is_string($route) && preg_match('#^/[a-zA-Z0-9/_-]+$#',$route),'ROUTE',__('Invalid Picsart action.','picsart-commerce')); $reset = $route === '/auth/reset-unreadable'; picsart_runtime_require(!$reset || ($request->get_method() === 'POST' && ($request->get_json_params()['acknowledged'] ?? null) === true), 'RESET', __('Confirm resetting the unreadable local workspace first.', 'picsart-commerce')); $store = new Picsart_Runtime_Store($reset); $result = picsart_runtime_dispatch($store,$route,$request); return is_wp_error($result) ? $result : new WP_REST_Response($result,200,['Cache-Control'=>'no-store']); } catch (Picsart_Runtime_Error $error) { return new WP_Error($error->reason,$error->getMessage(),['status'=>$error->status]); } catch (Throwable $error) { return new WP_Error('RUNTIME',__('This action could not finish. Check its result before trying again.','picsart-commerce'),['status'=>500]); } finally { if ($store) { picsart_runtime_schedule($store); $store->release(); } } }