From cf71bcfd9641ab0804fdb3d624499c4dbec46987 Mon Sep 17 00:00:00 2001 From: DJP Date: Fri, 17 Oct 2025 16:43:26 -0400 Subject: [PATCH] =?UTF-8?q?Complete=20workflow=20V3:=20Add=20Upload=20(A2?= =?UTF-8?q?=E2=86=92A3)=20and=20Rework=20(A5=E2=86=92A6)=20workflows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NEW: Upload Workflow (A2→A3) - Load campaigns with status A2 (assets sent to agency) - Select campaign and find upload target folder (Final Assets) - Multi-file upload with drag-drop interface - AssetUploader class with multipart/form-data support - Upload files with metadata to DAM - Update status to A3 after upload NEW: Rework Workflow (A5→A6) - Load campaigns with status A5 (rework needed from agency) - Select campaign and get rework assets - Download individual or bulk rework assets - Beautiful metadata display - Update status to A6 when rework assets received Features: - Three complete workflow tabs (Download, Upload, Rework) - Status transitions: A1→A2, A2→A3, A5→A6 - Session-based workflow state management - Clear workflow data button for each tab - Campaign selection with visual feedback - Download confirmed working (tested with real asset) - OAuth2 auto-refresh throughout all workflows Technical: - AssetUploader class for multipart file uploads - Proper metadata JSON structure for uploads - Support for multiple file formats (JPG, PNG, PDF, AI, PSD, etc.) - Error handling for each workflow step - Beautiful UI with color-coded status badges Ready for testing with real campaign data! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/AssetUploader.php | 172 +++++++++++++ workflow_v3.php | 546 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 709 insertions(+), 9 deletions(-) create mode 100644 src/AssetUploader.php diff --git a/src/AssetUploader.php b/src/AssetUploader.php new file mode 100644 index 0000000..6a1539e --- /dev/null +++ b/src/AssetUploader.php @@ -0,0 +1,172 @@ +apiClient = $apiClient; + $this->targetFolderId = $targetFolderId; + } + + /** + * Upload a file to DAM with metadata + * + * @param string $filePath Local file path + * @param string $folderId Target folder ID (campaign folder) + * @param array $metadata Optional metadata to set + * @return array Upload result + */ + public function uploadFile($filePath, $folderId, $metadata = []) + { + if (!file_exists($filePath)) { + return [ + 'success' => false, + 'error' => 'File not found: ' . $filePath + ]; + } + + $filename = basename($filePath); + $mimeType = mime_content_type($filePath); + + // Build multipart form data + $boundary = '----WebKitFormBoundary' . uniqid(); + + // Build asset representation JSON + $assetRepresentation = $this->buildAssetRepresentation($filename, $metadata); + + // Build multipart body + $body = ''; + + // Add asset_representation part + $body .= "--{$boundary}\r\n"; + $body .= "Content-Disposition: form-data; name=\"asset_representation\"\r\n"; + $body .= "Content-Type: application/json\r\n\r\n"; + $body .= json_encode($assetRepresentation) . "\r\n"; + + // Add parent_folder_id part + $body .= "--{$boundary}\r\n"; + $body .= "Content-Disposition: form-data; name=\"parent_folder_id\"\r\n\r\n"; + $body .= $folderId . "\r\n"; + + // Add file part + $body .= "--{$boundary}\r\n"; + $body .= "Content-Disposition: form-data; name=\"file\"; filename=\"{$filename}\"\r\n"; + $body .= "Content-Type: {$mimeType}\r\n\r\n"; + $body .= file_get_contents($filePath) . "\r\n"; + + $body .= "--{$boundary}--\r\n"; + + // Make upload request + $request = [ + 'method' => 'POST', + 'url' => '/v6/assets', + 'body' => $body + ]; + + // Set multipart content-type header + $this->apiClient->setHeader('Content-Type', "multipart/form-data; boundary={$boundary}"); + + $response = $this->apiClient->executeRequest($request); + + if ($response['success'] && $response['http_code'] == 201) { + $responseData = json_decode($response['body'], true); + + return [ + 'success' => true, + 'asset_id' => $responseData['asset_resource_list']['asset_resource'][0]['asset']['asset_id'] ?? null, + 'filename' => $filename, + 'response' => $responseData + ]; + } + + return [ + 'success' => false, + 'error' => 'Upload failed', + 'http_code' => $response['http_code'], + 'response' => $response['body'] + ]; + } + + /** + * Build asset representation for upload + */ + private function buildAssetRepresentation($filename, $metadata = []) + { + $assetRep = [ + 'asset_resource' => [ + 'asset' => [ + 'metadata' => [ + 'metadata_element_list' => [] + ] + ] + ] + ]; + + // Add filename metadata + $assetRep['asset_resource']['asset']['metadata']['metadata_element_list'][] = [ + 'id' => 'ARTESIA.FIELD.ASSET NAME', + 'type' => 'com.artesia.metadata.MetadataField', + 'value' => [ + 'cascading_domain_value' => false, + 'domain_value' => false, + 'value' => [ + 'type' => 'string', + 'value' => $filename + ] + ] + ]; + + // Add custom metadata fields + foreach ($metadata as $fieldId => $fieldValue) { + $assetRep['asset_resource']['asset']['metadata']['metadata_element_list'][] = [ + 'id' => $fieldId, + 'type' => 'com.artesia.metadata.MetadataField', + 'value' => [ + 'cascading_domain_value' => false, + 'domain_value' => false, + 'value' => [ + 'type' => 'string', + 'value' => $fieldValue + ] + ] + ]; + } + + return $assetRep; + } + + /** + * Upload multiple files + */ + public function uploadMultipleFiles($files, $folderId, $metadata = []) + { + $results = [ + 'total' => count($files), + 'successful' => 0, + 'failed' => 0, + 'details' => [] + ]; + + foreach ($files as $file) { + $result = $this->uploadFile($file, $folderId, $metadata); + + if ($result['success']) { + $results['successful']++; + } else { + $results['failed']++; + } + + $results['details'][] = $result; + } + + return $results; + } +} diff --git a/workflow_v3.php b/workflow_v3.php index f488d63..0e32cb6 100644 --- a/workflow_v3.php +++ b/workflow_v3.php @@ -24,6 +24,7 @@ require_once 'src/AssetDownloader.php'; require_once 'src/MetadataExtractor.php'; require_once 'src/StatusManager.php'; require_once 'src/ApiClient.php'; +require_once 'src/AssetUploader.php'; $configV3 = new ConfigV3(); $errors = $configV3->validate(); @@ -163,11 +164,158 @@ if ($_POST && $testRunner) { $currentTab = 'upload'; break; + case 'select_campaign_a2': + $campaignId = $_POST['campaign_id'] ?? ''; + if ($campaignId && isset($results['a2_campaigns'])) { + foreach ($results['a2_campaigns'] as $campaign) { + if ($campaign['asset_id'] === $campaignId) { + $results['selected_campaign_a2'] = $campaign; + $success = "Campaign selected: " . $campaign['campaign_name']; + $currentTab = 'upload'; + break; + } + } + } + break; + + case 'get_upload_folder': + if (isset($results['selected_campaign_a2'])) { + $campaignId = $results['selected_campaign_a2']['asset_id']; + // Find Final Assets or Localized Assets folder + $uploadFolderId = findUploadFolder($testRunner, $campaignId, $configV3); + + if ($uploadFolderId) { + $results['upload_folder_id'] = $uploadFolderId; + $success = "Found upload target folder"; + } else { + $error = "Upload folder not found in campaign"; + } + } + $currentTab = 'upload'; + break; + + case 'upload_files': + if (isset($_FILES['upload_files']) && isset($results['upload_folder_id'])) { + $uploadResults = uploadFiles($testRunner, $_FILES['upload_files'], $results['upload_folder_id']); + $results['upload_results'] = $uploadResults; + + if ($uploadResults['successful'] > 0) { + $success = "Uploaded {$uploadResults['successful']} of {$uploadResults['total']} files"; + } else { + $error = "All uploads failed"; + } + } + $currentTab = 'upload'; + break; + + case 'update_status_to_a3': + if (isset($results['selected_campaign_a2'])) { + $statusManager = createStatusManager($testRunner); + $statusResult = $statusManager->updateCampaignStatus( + $results['selected_campaign_a2']['asset_id'], + 'A3' + ); + + if ($statusResult['success']) { + $results['status_update_a3'] = $statusResult; + $success = "Campaign status updated to A3: Localized Asset received from Agency"; + } else { + $error = "Failed to update status"; + } + } + $currentTab = 'upload'; + break; + case 'load_campaigns_a5': $results['a5_campaigns'] = loadCampaignsByStatus($testRunner, 'A5'); $success = "Loaded " . count($results['a5_campaigns']) . " campaigns with status A5"; $currentTab = 'rework'; break; + + case 'select_campaign_a5': + $campaignId = $_POST['campaign_id'] ?? ''; + if ($campaignId && isset($results['a5_campaigns'])) { + foreach ($results['a5_campaigns'] as $campaign) { + if ($campaign['asset_id'] === $campaignId) { + $results['selected_campaign_a5'] = $campaign; + $success = "Campaign selected: " . $campaign['campaign_name']; + $currentTab = 'rework'; + break; + } + } + } + break; + + case 'get_rework_assets': + if (isset($results['selected_campaign_a5'])) { + $campaignId = $results['selected_campaign_a5']['asset_id']; + // Find folder with rework assets (could be Final Assets or Rework folder) + $reworkFolderId = findUploadFolder($testRunner, $campaignId, $configV3); + + if ($reworkFolderId) { + $results['rework_folder_id'] = $reworkFolderId; + $results['rework_assets'] = getAssetsFromFolder($testRunner, $reworkFolderId); + $success = "Found " . count($results['rework_assets']) . " assets needing rework"; + } else { + $error = "Rework assets folder not found"; + } + } + $currentTab = 'rework'; + break; + + case 'download_rework_asset': + $assetId = $_POST['asset_id'] ?? ''; + $filename = $_POST['filename'] ?? ''; + + if ($assetId && isset($results['selected_campaign_a5'])) { + $assetData = null; + if (isset($results['rework_assets'])) { + foreach ($results['rework_assets'] as $asset) { + if ($asset['asset_id'] === $assetId) { + $assetData = $asset; + break; + } + } + } + + $result = downloadAsset($testRunner, $assetId, $filename, $results['selected_campaign_a5']['campaign_id'], $assetData); + $results['last_rework_download'] = $result; + + if ($result['success']) { + $success = "Downloaded rework asset: " . $result['filename']; + } else { + $error = "Download failed: " . $result['error']; + } + } + $currentTab = 'rework'; + break; + + case 'download_all_rework': + if (isset($results['rework_assets']) && isset($results['selected_campaign_a5'])) { + $downloadResults = downloadAllAssets($testRunner, $results['rework_assets'], $results['selected_campaign_a5']['campaign_id']); + $results['rework_bulk_download'] = $downloadResults; + $success = "Downloaded {$downloadResults['successful']} of {$downloadResults['total']} rework assets"; + } + $currentTab = 'rework'; + break; + + case 'update_status_to_a6': + if (isset($results['selected_campaign_a5'])) { + $statusManager = createStatusManager($testRunner); + $statusResult = $statusManager->updateCampaignStatus( + $results['selected_campaign_a5']['asset_id'], + 'A6' + ); + + if ($statusResult['success']) { + $results['status_update_a6'] = $statusResult; + $success = "Campaign status updated to A6: Assets to be reworked received by the Agency"; + } else { + $error = "Failed to update status"; + } + } + $currentTab = 'rework'; + break; } } catch (Exception $e) { $error = "Action failed: " . $e->getMessage(); @@ -509,6 +657,108 @@ function createStatusManager($testRunner) return new StatusManager($apiClient); } +function findUploadFolder($testRunner, $campaignId, $configV3) +{ + $finalFolderName = $configV3->getFolderName('final_assets'); + + $requests = $testRunner->getAvailableRequests(); + + foreach ($requests as $index => $request) { + $name = strtolower($request['name']); + if (strpos($name, 'retrieve master asset folder') !== false || + strpos($name, 'master asset folder and final') !== false) { + + $modifiedRequest = $request; + $url = is_array($request['request']['url']) ? $request['request']['url']['raw'] : $request['request']['url']; + $url = preg_replace('/folders\/[a-f0-9]+\//', "folders/{$campaignId}/", $url); + $url = str_replace('{{baseUrl}}', $configV3->getBaseUrl(), $url); + + if (is_array($modifiedRequest['request']['url'])) { + $modifiedRequest['request']['url']['raw'] = $url; + } else { + $modifiedRequest['request']['url'] = $url; + } + + $result = $testRunner->runSingleTest($modifiedRequest, $index); + + if ($result['status'] === 'PASS') { + $data = json_decode($result['response']['body'], true); + $folders = $data['folder_children']['asset_list'] ?? []; + + foreach ($folders as $folder) { + $folderName = extractFolderName($folder); + // Look for Final Assets or Localized folder + if (strpos($folderName, 'Final') !== false || + strpos($folderName, 'Localized') !== false || + $folderName === $finalFolderName) { + return $folder['asset_id']; + } + } + } + break; + } + } + + return null; +} + +function uploadFiles($testRunner, $uploadedFiles, $folderId) +{ + $apiClient = new ApiClient(); + $configV3 = new ConfigV3(); + $apiClient->setBaseUrl($configV3->getBaseUrl()); + + $oauth2Handler = new ReflectionProperty($testRunner, 'oauth2Handler'); + $oauth2Handler->setAccessible(true); + $oauth2HandlerInstance = $oauth2Handler->getValue($testRunner); + if ($oauth2HandlerInstance) { + $apiClient->setHeader('Authorization', $oauth2HandlerInstance->getAuthHeader()); + } + + $uploader = new AssetUploader($apiClient, $folderId); + + $results = [ + 'total' => 0, + 'successful' => 0, + 'failed' => 0, + 'details' => [] + ]; + + // Handle multiple file upload + if (isset($uploadedFiles['tmp_name'])) { + $fileCount = is_array($uploadedFiles['tmp_name']) ? count($uploadedFiles['tmp_name']) : 1; + $results['total'] = $fileCount; + + for ($i = 0; $i < $fileCount; $i++) { + $tmpName = is_array($uploadedFiles['tmp_name']) ? $uploadedFiles['tmp_name'][$i] : $uploadedFiles['tmp_name']; + $fileName = is_array($uploadedFiles['name']) ? $uploadedFiles['name'][$i] : $uploadedFiles['name']; + $fileError = is_array($uploadedFiles['error']) ? $uploadedFiles['error'][$i] : $uploadedFiles['error']; + + if ($fileError !== UPLOAD_ERR_OK) { + $results['details'][] = [ + 'success' => false, + 'filename' => $fileName, + 'error' => 'Upload error code: ' . $fileError + ]; + $results['failed']++; + continue; + } + + $result = $uploader->uploadFile($tmpName, $folderId); + + if ($result['success']) { + $results['successful']++; + } else { + $results['failed']++; + } + + $results['details'][] = $result; + } + } + + return $results; +} + $oauth2Status = $testRunner ? $testRunner->getOAuth2Status() : null; $envInfo = $configV3->getEnvironmentInfo(); ?> @@ -1076,18 +1326,132 @@ $envInfo = $configV3->getEnvironmentInfo();

📤 Upload Workflow: A2 → A3

- Load campaigns with status A2, upload processed assets, and update status to A3 + Load campaigns with status A2 (assets sent to agency), upload processed files, and update status to A3

-
+
-
- ℹ️ Coming Soon: Upload functionality will be implemented in next phase -
+ +
+ + + +
+ + + +

Found Campaigns

+
+ +
+

+

Campaign ID:

+

Asset ID:

+

Brand:

+

Market:

+

A2 - Assets Sent to Agency

+
+ +
+ +
+ + + + +
+ + + +
+ Selected Campaign: + + () +
+ + +
+ + + +
+ + + + +
+ ✅ Upload target folder found +
+ +

Upload Processed Files

+
+ + + +
+ + +

+ Accepted formats: JPG, PNG, GIF, PDF, AI, PSD, EPS, TIFF +

+
+ + +
+ + + + +
+ Upload Results: + of files uploaded successfully + + +
+ View upload details +
+ $detail): ?> +
+ + ✅ +
Asset ID: + + ❌ +
Error: + +
+ +
+
+ +
+ + 0): ?> +
+

✅ Update Status to A3

+

+ Files have been uploaded. Update the campaign status to A3 + (Localized Asset received from Agency) +

+
+ + + +
+
+ +
@@ -1097,15 +1461,159 @@ $envInfo = $configV3->getEnvironmentInfo(); Load campaigns with status A5 (rework needed), download assets for rework, and update status to A6

-
+
-
- ℹ️ Coming Soon: Rework workflow will be implemented in next phase -
+ +
+ + + +
+ + + +

Found Campaigns Needing Rework

+
+ +
+

+

Campaign ID:

+

Asset ID:

+

Brand:

+

Market:

+

A5 - Rework Needed

+
+ +
+ +
+ + + + +
+ + + +
+ Selected Campaign: + + () +
+ +
+ + + +
+ + + +

Rework Assets ( files)

+ +
+ + + +
+ +
+ $asset): ?> + +
+
+
+ ID:
+ Type: + + | Size: bytes + +
+
+
+ + + + + +
+ +
+
+ + +
+ +
+

⚠️ Update Status to A6

+

+ Once rework assets are downloaded, update the campaign status to A6 + (Assets to be reworked received by the Agency) +

+
+ + + +
+
+ + + + +
+ + ✅ Download Successful: +
Size: bytes + + ❌ Download Failed: + +
+ + + +
+ Bulk Download Results: + of rework assets downloaded +
+ @@ -1140,6 +1648,26 @@ $envInfo = $configV3->getEnvironmentInfo(); metadataDiv.style.display = metadataDiv.style.display === 'none' ? 'block' : 'none'; } } + + function selectCampaignA2(campaignId) { + document.querySelectorAll('.campaign-card').forEach(card => { + card.classList.remove('selected'); + }); + + event.currentTarget.classList.add('selected'); + document.getElementById('selected-campaign-a2-id').value = campaignId; + document.getElementById('select-campaign-a2-btn').disabled = false; + } + + function selectCampaignA5(campaignId) { + document.querySelectorAll('.campaign-card').forEach(card => { + card.classList.remove('selected'); + }); + + event.currentTarget.classList.add('selected'); + document.getElementById('selected-campaign-a5-id').value = campaignId; + document.getElementById('select-campaign-a5-btn').disabled = false; + }