get_header('X-WP-Nonce'), 'wp_rest') ? true : picsart_video_error(__('Media permission and a valid session are required.', 'picsart-commerce'),403); } function picsart_commerce_image($id) { return get_post_type($id)==='attachment' && wp_attachment_is_image($id) && current_user_can('edit_post',$id); } function picsart_commerce_target($type,$id) { if ($type==='term') return current_user_can('edit_term',$id) && get_term($id) && !is_wp_error(get_term($id)); if (!in_array($type,['featured','variation','gallery'],true) || !current_user_can('edit_post',$id)) return false; if ($type==='gallery') return get_post_type($id)==='product'; if ($type==='variation') return get_post_type($id)==='product_variation'; return (bool)get_post($id) && post_type_supports(get_post_type($id),'thumbnail'); } function picsart_commerce_value($type,$id) { return $type==='term' ? absint(get_term_meta($id,'thumbnail_id',true)) : ($type==='gallery' ? (string)get_post_meta($id,'_product_image_gallery',true) : absint(get_post_meta($id,'_thumbnail_id',true))); } function picsart_commerce_write($type,$id,$value,$expected) { global $wpdb; $kind=$type==='term'?'term':'post';$key=$type==='term'?'thumbnail_id':($type==='gallery'?'_product_image_gallery':'_thumbnail_id'); $table=$kind==='term'?$wpdb->termmeta:$wpdb->postmeta;$column=$kind.'_id'; $raw=get_metadata($kind,$id,$key,true);$normalized=$type==='gallery'?(string)$raw:absint($raw); if ($normalized!==$expected) return false; if (metadata_exists($kind,$id,$key)) { // Atomic comparison is required; update_metadata ignores an empty previous value. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.SlowDBQuery -- Indexed object ID bounds this compare-and-swap to one object; metadata cache invalidated immediately below. $changed=$wpdb->update($table,['meta_value'=>maybe_serialize($value)],[$column=>$id,'meta_key'=>$key,'meta_value'=>maybe_serialize($raw)]); } else { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Atomic insert-if-absent; WordPress add_metadata uses a separate non-atomic existence check. $changed=$wpdb->query($wpdb->prepare('INSERT INTO %i (%i, meta_key, meta_value) SELECT %d, %s, %s WHERE NOT EXISTS (SELECT 1 FROM %i WHERE %i = %d AND meta_key = %s)',$table,$column,$id,$key,maybe_serialize($value),$table,$column,$id,$key)); } wp_cache_delete($id,$kind.'_meta');return $changed===1; } add_action('rest_api_init',function(){ foreach (['media-bytes'=>['GET','picsart_commerce_bytes'],'image-restores'=>['GET','picsart_commerce_restores'],'media-source'=>['GET','picsart_commerce_source'],'image-save'=>['POST','picsart_commerce_save'],'image-apply'=>['POST','picsart_commerce_apply'],'image-restore'=>['POST','picsart_commerce_restore'],'preferences'=>[['GET','POST'],'picsart_commerce_preferences'],'blog-draft'=>['POST','picsart_commerce_draft']] as $route=>$spec) register_rest_route('picsart/v1','/'.$route,['methods'=>$spec[0],'permission_callback'=>'picsart_commerce_permission','callback'=>$spec[1]]); }); function picsart_commerce_source($r) { $id=absint($r['attachmentId']); if (!picsart_commerce_image($id)) return picsart_video_error(__('Image unavailable.','picsart-commerce'),403); $size=wp_get_attachment_image_src($id,'full'); return ['attachmentId'=>$id,'url'=>wp_get_attachment_url($id),'width'=>$size[1]??0,'height'=>$size[2]??0,'title'=>get_the_title($id),'caption'=>get_post_field('post_excerpt',$id),'alt'=>get_post_meta($id,'_wp_attachment_image_alt',true)]; } function picsart_commerce_save($r) { $source=absint($r['sourceId']); if (!picsart_commerce_image($source)) return picsart_video_error(__('You cannot edit this source.','picsart-commerce'),403); $request=(string)$r['requestId']; if (!preg_match('/^[A-Za-z0-9_-]{16,100}$/',$request)) return picsart_video_error(__('A stable request ID is required.','picsart-commerce')); $files=$r->get_file_params(); $file=$files['image']??null; if (!$file || $file['error']!==UPLOAD_ERR_OK || !is_uploaded_file($file['tmp_name']) || $file['size']>20*1024*1024) return picsart_video_error(__('Choose an image up to 20 MB.','picsart-commerce')); $mime=wp_get_image_mime($file['tmp_name']); $ext=['image/jpeg'=>'jpg','image/png'=>'png','image/webp'=>'webp']; $dimensions=getimagesize($file['tmp_name']); if (!isset($ext[$mime]) || !$dimensions || $dimensions[0]*$dimensions[1]>40000000) return picsart_video_error(__('Choose a JPEG, PNG or WebP up to 40 megapixels.','picsart-commerce')); $operation=sanitize_key($r['operation']??'edit'); if (!in_array($operation,['edit','watermark','brand-mark'],true)) return picsart_video_error(__('Unsupported local image operation.','picsart-commerce')); $recipe=(string)($r['recipe']??''); if (strlen($recipe)>8192) return picsart_video_error(__('Edit description is too large.','picsart-commerce')); $recipe_data=$recipe ? json_decode($recipe,true) : []; if (!is_array($recipe_data)) return picsart_video_error(__('Invalid edit description.','picsart-commerce')); $fingerprint=hash('sha256',$source.'|'.$operation.'|'.$recipe.'|'.hash_file('sha256',$file['tmp_name'])); $key='_picsart_commerce_save_'.hash('sha256',get_current_user_id().'|'.$request); $old=get_option($key); if ($old) { if (($old['fingerprint']??'')!==$fingerprint) return picsart_video_error(__('Request ID already used for another edit.','picsart-commerce'),409); if (!empty($old['result']) && picsart_commerce_image($old['result']['attachmentId'])) return $old['result']; return picsart_video_error(__('Save is pending. Check Media Library before retrying.','picsart-commerce'),409); } if (!picsart_receipt_add($key,['fingerprint'=>$fingerprint],'',false)) return picsart_video_error(__('Save already started.','picsart-commerce'),409); require_once ABSPATH.'wp-admin/includes/file.php'; require_once ABSPATH.'wp-admin/includes/media.php'; require_once ABSPATH.'wp-admin/includes/image.php'; $file['name']=sanitize_file_name(get_the_title($source).'-picsart.'.$ext[$mime]); $id=media_handle_sideload($file,0); if (is_wp_error($id)) { delete_option($key); return $id; } update_post_meta($id,'_picsart_source_id',$source); update_post_meta($id,'_picsart_operation',$operation); update_post_meta($id,'_picsart_recipe',wp_slash(wp_json_encode($recipe_data))); update_post_meta($id,'_picsart_marked',in_array($operation,['watermark','brand-mark'],true) || (bool)get_post_meta($source,'_picsart_marked',true) || in_array(get_post_meta($source,'_picsart_operation',true),['watermark','brand-mark'],true)); update_post_meta($id,'_wp_attachment_image_alt',get_post_meta($source,'_wp_attachment_image_alt',true)); $original=get_post($source); wp_update_post(['ID'=>$id,'post_excerpt'=>$original->post_excerpt,'post_content'=>$original->post_content]); $result=['attachmentId'=>$id,'url'=>wp_get_attachment_url($id),'sourceId'=>$source]; update_option($key,['fingerprint'=>$fingerprint,'result'=>$result],false); return $result; } function picsart_commerce_apply($r) { $type=sanitize_key($r['targetType']); $target=absint($r['targetId']); $image=absint($r['attachmentId']); if (!picsart_commerce_target($type,$target) || !picsart_commerce_image($image)) return picsart_video_error(__('You cannot change this destination.','picsart-commerce'),403); if (((bool)get_post_meta($image,'_picsart_marked',true) || in_array(get_post_meta($image,'_picsart_operation',true),['watermark','brand-mark'],true)) && in_array(get_post_type($target),['product','product_variation'],true)) return picsart_video_error(__('Marked copies cannot replace product listing images. Use the clean original.','picsart-commerce'),409); $lock='_picsart_commerce_target_'.hash('sha256',$type.'|'.$target); if (!picsart_receipt_add($lock,time(),'',false)) return picsart_video_error(__('Another change is pending.','picsart-commerce'),409); try { $before=picsart_commerce_value($type,$target); $expected=absint($r['expectedPreviousId']); $after=$image; $intent='_picsart_commerce_intent_'.hash('sha256',get_current_user_id().'|'.$type.'|'.$target.'|'.$image.'|'.$expected); $prior=get_option($intent); if (is_array($prior) && $before===$prior['current']) return $prior; if ($type==='gallery') { $ids=array_values(array_filter(array_map('absint',explode(',',$before)))); if ($expected && !in_array($expected,$ids,true)) return picsart_video_error(__('Gallery changed. Review it again.','picsart-commerce'),409); if ($expected) $ids=array_map(fn($id)=>$id===$expected?$image:$id,$ids); elseif (!in_array($image,$ids,true)) $ids[]=$image; $after=implode(',',array_unique($ids)); } elseif ($before!==$expected) return picsart_video_error(__('Image changed. Review it again.','picsart-commerce'),409); $receipt=wp_generate_uuid4(); $record=['user'=>get_current_user_id(),'type'=>$type,'target'=>$target,'before'=>$before,'after'=>$after]; if (!picsart_receipt_add('_picsart_commerce_apply_'.$receipt,$record,'',false)) return picsart_video_error(__('Could not reserve restore receipt. Nothing changed.','picsart-commerce'),409); if (!picsart_commerce_write($type,$target,$after,$before)) return picsart_video_error(__('The destination changed during this edit. Nothing was overwritten. Review it again.','picsart-commerce'),409); if (picsart_commerce_value($type,$target)!==$after) return picsart_video_error(__('The destination did not update. Review it before trying again.','picsart-commerce'),500); if ($type==='term') add_term_meta($target,'_picsart_restore_receipt',$receipt); else add_post_meta($target,'_picsart_restore_receipt',$receipt); $result=['receiptId'=>$receipt,'attachmentId'=>$image,'previous'=>$before,'current'=>$after]; if (!get_option($intent)) picsart_receipt_add($intent,$result,'',false); else update_option($intent,$result,false); return $result; } finally { delete_option($lock); } } function picsart_commerce_restore($r) { $receipt=sanitize_text_field($r['receiptId']); if (!wp_is_uuid($receipt)) return picsart_video_error(__('Invalid restore receipt.','picsart-commerce')); $record=get_option('_picsart_commerce_apply_'.$receipt); if (!$record || $record['user']!==get_current_user_id() || !picsart_commerce_target($record['type'],$record['target'])) return picsart_video_error(__('Restore unavailable.','picsart-commerce'),403); $originals=$record['type']==='gallery'?array_filter(array_map('absint',explode(',',$record['before']))):array_filter([absint($record['before'])]);foreach ($originals as $original) if (!picsart_commerce_image($original)) return picsart_video_error(__('The previous image is unavailable. The current image was not changed.','picsart-commerce'),409); $lock='_picsart_commerce_target_'.hash('sha256',$record['type'].'|'.$record['target']); if (!picsart_receipt_add($lock,time(),'',false)) return picsart_video_error(__('Another change is pending.','picsart-commerce'),409); try { if (picsart_commerce_value($record['type'],$record['target'])!==$record['after']) return picsart_video_error(__('Destination changed since this edit. Nothing restored.','picsart-commerce'),409); if (!picsart_commerce_write($record['type'],$record['target'],$record['before'],$record['after'])) return picsart_video_error(__('The destination changed during restore. Review it again.','picsart-commerce'),409); if (picsart_commerce_value($record['type'],$record['target'])!==$record['before']) return picsart_video_error(__('The previous image could not be restored. Review the destination and try again.','picsart-commerce'),500); return ['restored'=>true]; } finally { delete_option($lock); } } function picsart_commerce_consent() { $prefs=get_user_meta(get_current_user_id(),'_picsart_preferences',true); return is_array($prefs) && !empty($prefs['consent']); } function picsart_commerce_preferences($r) { $key='_picsart_preferences'; $prefs=get_user_meta(get_current_user_id(),$key,true); if (!is_array($prefs)) $prefs=['consent'=>false,'notifications'=>true,'dismissed'=>false]; if ($r->get_method()==='POST') { foreach (['consent','notifications','dismissed'] as $field) if ($r->has_param($field)) $prefs[$field]=rest_sanitize_boolean($r[$field]); update_user_meta(get_current_user_id(),$key,$prefs); } return $prefs; } function picsart_commerce_draft($r) { if (!current_user_can('edit_posts')) return picsart_video_error(__('Draft permission required.','picsart-commerce'),403); $id=absint($r['attachmentId']); if (get_post_type($id)!=='attachment' || !current_user_can('edit_post',$id) || !in_array(get_post_mime_type($id),['image/png','image/jpeg','image/webp','video/mp4','video/webm'],true)) return picsart_video_error(__('Media unavailable.','picsart-commerce'),403); $title=sanitize_text_field($r['title']); if (!$title) return picsart_video_error(__('Enter a draft title.','picsart-commerce')); $request=(string)$r['requestId']; if (!preg_match('/^[A-Za-z0-9_-]{16,100}$/',$request)) return picsart_video_error(__('A stable request ID is required.','picsart-commerce')); $key='_picsart_commerce_draft_'.hash('sha256',get_current_user_id().'|'.$request); $fingerprint=hash('sha256',$id.'|'.$title.'|'.wp_kses_post($r['content']??'')); $old=get_option($key); if ($old) { if (($old['fingerprint']??'')!==$fingerprint) return picsart_video_error(__('Request ID already used.','picsart-commerce'),409); if (!empty($old['result']) && current_user_can('edit_post',$old['result']['postId'])) return $old['result']; return picsart_video_error(__('Draft creation pending. Check your drafts.','picsart-commerce'),409); } if (!picsart_receipt_add($key,['fingerprint'=>$fingerprint],'',false)) return picsart_video_error(__('Draft already started.','picsart-commerce'),409); $media=wp_attachment_is_image($id)?wp_get_attachment_image($id,'large'):wp_video_shortcode(['src'=>wp_get_attachment_url($id),'preload'=>'metadata']); $post=wp_insert_post(['post_type'=>'post','post_status'=>'draft','post_title'=>$title,'post_content'=>wp_kses_post($r['content']??'').wp_kses_post($media),'post_author'=>get_current_user_id()],true); if (is_wp_error($post)) { delete_option($key); return $post; } set_post_thumbnail($post,$id); $result=['postId'=>$post,'editUrl'=>get_edit_post_link($post,'raw')]; update_option($key,['fingerprint'=>$fingerprint,'result'=>$result],false); return $result; } function picsart_commerce_link($id,$post=0,$term=0,$target='') { return add_query_arg(['_picsart_context'=>wp_create_nonce('picsart_navigation'),'page'=>'picsart-studio','view'=>'images','attachment_id'=>$id,'post_id'=>$post,'term_id'=>$term,'target'=>$target],admin_url('admin.php')); } function picsart_commerce_button($id,$post=0,$term=0,$target='') { return ''.esc_html__('Edit with Picsart','picsart-commerce').''; } function picsart_media_ai_actions($id) { $args=['_picsart_context'=>wp_create_nonce('picsart_navigation'),'attachment_id'=>absint($id)]; $image=add_query_arg(array_merge($args,['page'=>'picsart-image-generation']),admin_url('admin.php')); $video=add_query_arg(array_merge($args,['page'=>'picsart-studio']),admin_url('admin.php')); return ''.esc_html__('Edit with AI','picsart-commerce').' '.esc_html__('Generate video','picsart-commerce').''; } add_filter('media_row_actions',function($actions,$post){ if (picsart_commerce_image($post->ID)) $actions['picsart']=picsart_media_ai_actions($post->ID); return $actions; },10,2); add_filter('attachment_fields_to_edit',function($fields,$post){ if (picsart_commerce_image($post->ID)) $fields['picsart']=['label'=>'Picsart','input'=>'html','html'=>picsart_media_ai_actions($post->ID)]; return $fields; },10,2); add_filter('admin_post_thumbnail_html',function($html,$id){ $image=get_post_thumbnail_id($id); return $image && current_user_can('edit_post',$id) ? $html.picsart_commerce_button($image,$id,0,get_post_type($id)==='product_variation'?'variation':'featured') : $html; },10,2); add_action('init',function(){ foreach (get_taxonomies(['public'=>true]) as $tax) add_action($tax.'_edit_form_fields',function($term){ $image=absint(get_term_meta($term->term_id,'thumbnail_id',true)); if ($image && current_user_can('edit_term',$term->term_id)) echo '
'.esc_html(get_the_title($id)).' '.esc_html__('Edit with AI','picsart-commerce').'
'; } } add_action('woocommerce_product_after_variable_attributes',function($index,$data,$variation){ $id=get_post_thumbnail_id($variation->ID); if ($id && current_user_can('edit_post',$variation->ID)) echo wp_kses_post(picsart_commerce_button($id,$variation->ID,0,'variation')); },10,3); add_shortcode('picsart_media',function($attributes){ $a=shortcode_atts(['id'=>0],$attributes); $id=absint($a['id']); if (get_post_type($id)!=='attachment' || !in_array(get_post_status($id),['inherit','publish'],true)) return ''; $parent=wp_get_post_parent_id($id); if ($parent && get_post_status($parent)!=='publish') return ''; if (wp_attachment_is_image($id)) return wp_get_attachment_image($id,'large'); if (in_array(get_post_mime_type($id),['video/mp4','video/webm'],true)) { $url=wp_get_attachment_url($id); return $url ? wp_video_shortcode(['src'=>esc_url($url),'preload'=>'metadata']) : ''; } return ''; }); add_action('init',function(){ register_block_type('picsart/media',['api_version'=>3,'attributes'=>['id'=>['type'=>'number','default'=>0],'url'=>['type'=>'string'],'mediaType'=>['type'=>'string'],'alt'=>['type'=>'string']],'render_callback'=>fn($a)=>do_shortcode('[picsart_media id="'.absint($a['id']??0).'"]')]); }); add_action('enqueue_block_editor_assets',function(){ wp_enqueue_script('picsart-native',plugins_url('../assets/native.js',__FILE__),['wp-hooks','wp-element','wp-components','wp-block-editor','wp-blocks','wp-i18n','wp-server-side-render','wp-data','wp-api-fetch','wp-rich-text'],filemtime(__DIR__.'/../assets/native.js'),true); wp_localize_script('picsart-native','picsartNative',['tools'=>picsart_commerce_link(0)]); }); add_action('admin_enqueue_scripts',function($hook){ if ($hook==='toplevel_page_picsart-studio') wp_enqueue_media(); }); function picsart_onboarding_notice() { if (!current_user_can('upload_files')) return; $prefs=get_user_meta(get_current_user_id(),'_picsart_preferences',true); if (!empty($prefs['dismissed'])) return; // A stored OAuth connection is independent of the legacy consent preference. $connection=picsart_video_configured()?picsart_plugin_connection():null; if (is_array($connection) && $connection['auth']['authenticated'] && !$connection['auth']['requiresReconnect']) return; $check=is_wp_error($connection); echo ''.esc_html__('Picsart is installed.','picsart-commerce').' '.esc_html__('Connect your account to create and edit images and videos with Picsart Commerce.','picsart-commerce').' '.esc_html__('Connect Picsart','picsart-commerce').'