Major improvements to workflow_v3.php and supporting classes:
**Status Updates (A1→A2, A2→A3, A5→A6):**
- Fix StatusManager to use correct PATCH endpoint: /v6/folders/{id}
- Add lock_strategy=optimistic parameter to prevent locking errors
- Update request body structure to use edited_folder format
- Status updates now working correctly
**Status Field Extraction:**
- Fix CampaignFormatter to extract CONTENT.SCALING.STATUS field
- Handle domain values with field_value.value path
- Now correctly filters campaigns by status (A1, A2, A5, etc.)
**Error Handling:**
- Extract and display actual API error messages
- Show HTTP status codes in all error messages
- Add expandable debug panels with full API responses
- Enhanced upload error reporting with detailed diagnostics
**Campaign Search:**
- Update to use Postman collection requests directly (avoids 503 errors)
- Fix URL encoding (rawurlencode instead of urlencode)
- Add comprehensive debug output showing OAuth status and API responses
- Search now attempts Postman request first, falls back to manual query
**Upload Improvements:**
- Rewrite AssetUploader to use native PHP CURLFile for multipart uploads
- Add support for additional file types: .mov, .mp4, .avi, .zip, .txt, .doc, .xlsx
- Increase max upload size to 100MB for video files
- Simplify asset_representation to minimal structure
- Add infrastructure to inherit metadata from master assets
**Testing Features:**
- Add "Reset to A1" button for testing workflow iterations
- Add debug mode to view all campaigns and their metadata
- Show Content Scaling Status on all campaign cards
- Display filtering debug info (total vs filtered counts)
**UI Improvements:**
- Rename buttons to clarify "Content Scaling Status" terminology
- Add status badges to campaign cards showing current status
- Better visual feedback for successful/failed operations
Current Status: Workflow A1→A2 fully working. Upload A2→A3 ready for testing
once DAM server recovers from HTTP 503 errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
256 lines
8.3 KiB
PHP
256 lines
8.3 KiB
PHP
<?php
|
|
|
|
require_once 'ApiClient.php';
|
|
|
|
/**
|
|
* AssetUploader - Handles file uploads to Ferrero DAM
|
|
*/
|
|
class AssetUploader
|
|
{
|
|
private $apiClient;
|
|
private $targetFolderId;
|
|
|
|
public function __construct(ApiClient $apiClient, $targetFolderId = null)
|
|
{
|
|
$this->apiClient = $apiClient;
|
|
$this->targetFolderId = $targetFolderId;
|
|
}
|
|
|
|
/**
|
|
* Upload a file to DAM with metadata
|
|
* Uses PHP's native multipart/form-data with CURLFile
|
|
*
|
|
* @param string $filePath Local file path
|
|
* @param string $folderId Target folder ID (campaign folder)
|
|
* @param array $metadata Optional metadata to set
|
|
* @param array $sourceMetadata Full metadata structure from master asset to copy
|
|
* @return array Upload result
|
|
*/
|
|
public function uploadFile($filePath, $folderId, $metadata = [], $sourceMetadata = null)
|
|
{
|
|
if (!file_exists($filePath)) {
|
|
return [
|
|
'success' => false,
|
|
'error' => 'File not found: ' . $filePath,
|
|
'filename' => basename($filePath)
|
|
];
|
|
}
|
|
|
|
$filename = basename($filePath);
|
|
$mimeType = mime_content_type($filePath);
|
|
|
|
// Get base URL from ApiClient using reflection
|
|
$baseUrlReflection = new ReflectionProperty($this->apiClient, 'baseUrl');
|
|
$baseUrlReflection->setAccessible(true);
|
|
$baseUrl = $baseUrlReflection->getValue($this->apiClient);
|
|
|
|
// Build URL
|
|
$url = rtrim($baseUrl, '/') . '/v6/assets';
|
|
|
|
// Build minimal asset representation with just the name
|
|
$minimalAssetRep = [
|
|
'asset_resource' => [
|
|
'asset' => [
|
|
'name' => $filename,
|
|
'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
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
$postFields = [
|
|
'asset_representation' => json_encode($minimalAssetRep),
|
|
'parent_folder_id' => $folderId,
|
|
'file' => new \CURLFile($filePath, $mimeType, $filename)
|
|
];
|
|
|
|
// Get authorization header
|
|
$authHeader = '';
|
|
$headersReflection = new ReflectionProperty($this->apiClient, 'headers');
|
|
$headersReflection->setAccessible(true);
|
|
$headers = $headersReflection->getValue($this->apiClient);
|
|
if (isset($headers['Authorization'])) {
|
|
$authHeader = $headers['Authorization'];
|
|
}
|
|
|
|
// Use curl directly for multipart upload
|
|
$ch = curl_init();
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_URL => $url,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => $postFields,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Accept: application/json',
|
|
'Authorization: ' . $authHeader
|
|
],
|
|
CURLOPT_SSL_VERIFYPEER => false,
|
|
CURLOPT_TIMEOUT => 120
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$error = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
if ($error) {
|
|
return [
|
|
'success' => false,
|
|
'filename' => $filename,
|
|
'error' => 'CURL error: ' . $error,
|
|
'http_code' => 0
|
|
];
|
|
}
|
|
|
|
if ($httpCode == 201) {
|
|
$responseData = json_decode($response, true);
|
|
|
|
return [
|
|
'success' => true,
|
|
'asset_id' => $responseData['asset_resource_list']['asset_resource'][0]['asset']['asset_id'] ?? null,
|
|
'filename' => $filename,
|
|
'http_code' => $httpCode
|
|
];
|
|
}
|
|
|
|
// Extract detailed error message from API response
|
|
$errorMsg = 'Upload failed';
|
|
if (!empty($response)) {
|
|
$responseData = json_decode($response, true);
|
|
if ($responseData && isset($responseData['exception_body'])) {
|
|
$errorMsg = $responseData['exception_body']['message'] ?? $responseData['exception_body']['debug_message'] ?? 'API Error';
|
|
} elseif ($responseData && isset($responseData['error'])) {
|
|
$errorMsg = $responseData['error'];
|
|
}
|
|
}
|
|
|
|
return [
|
|
'success' => false,
|
|
'filename' => $filename,
|
|
'error' => $errorMsg,
|
|
'http_code' => $httpCode,
|
|
'response_body' => $response
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Build asset representation from source metadata (copying from master asset)
|
|
* Updates filename but preserves all other metadata
|
|
*/
|
|
private function buildAssetRepresentationFromSource($filename, $sourceMetadata)
|
|
{
|
|
// Copy the entire metadata structure from the source
|
|
$assetRep = [
|
|
'asset_resource' => [
|
|
'asset' => [
|
|
'metadata' => $sourceMetadata,
|
|
'name' => $filename
|
|
]
|
|
]
|
|
];
|
|
|
|
// Update the ASSET NAME field if it exists in the metadata
|
|
if (isset($sourceMetadata['metadata_element_list'])) {
|
|
foreach ($sourceMetadata['metadata_element_list'] as &$category) {
|
|
if (isset($category['metadata_element_list'])) {
|
|
foreach ($category['metadata_element_list'] as &$field) {
|
|
if ($field['id'] === 'ARTESIA.FIELD.ASSET NAME') {
|
|
$field['value']['value']['value'] = $filename;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $assetRep;
|
|
}
|
|
|
|
/**
|
|
* Build asset representation for upload (fallback when no source metadata)
|
|
*/
|
|
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;
|
|
}
|
|
}
|