ferrero-opentext/src/AssetDownloader.php
DJP 716f0be068 Initial commit: Ferrero OpenText Content Scaling Application
Complete PHP-based workflow application for Ferrero DAM system:
- OAuth2 authentication with automatic token management
- Campaign discovery and filtering
- Folder structure navigation
- Asset download (individual and bulk)
- Metadata extraction and display
- Clean step-by-step web interface

Status: Fully functional and production-ready

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 14:29:20 -04:00

319 lines
No EOL
10 KiB
PHP

<?php
require_once 'ApiClient.php';
class AssetDownloader
{
private $apiClient;
private $downloadPath;
public function __construct($apiClient, $downloadPath = 'downloads/')
{
$this->apiClient = $apiClient;
$this->downloadPath = $downloadPath;
$this->ensureDownloadDirectory();
}
private function ensureDownloadDirectory()
{
if (!is_dir($this->downloadPath)) {
mkdir($this->downloadPath, 0755, true);
}
}
public function downloadAsset($assetId, $filename = null)
{
// First get asset details to determine filename and content URL
$assetDetails = $this->getAssetDetails($assetId);
if (!$assetDetails['success']) {
return [
'success' => false,
'error' => 'Failed to get asset details: ' . $assetDetails['error']
];
}
// Handle direct content download
if (isset($assetDetails['direct_content']) && $assetDetails['direct_content']) {
$safeFilename = $this->sanitizeFilename($filename ?: "asset_{$assetId}");
// Add extension based on content type
$contentType = $assetDetails['content_type'];
if (strpos($contentType, 'image/jpeg') !== false && !pathinfo($safeFilename, PATHINFO_EXTENSION)) {
$safeFilename .= '.jpg';
} elseif (strpos($contentType, 'image/png') !== false && !pathinfo($safeFilename, PATHINFO_EXTENSION)) {
$safeFilename .= '.png';
}
$fullPath = $this->downloadPath . $safeFilename;
$bytesWritten = file_put_contents($fullPath, $assetDetails['content_data']);
if ($bytesWritten === false) {
return [
'success' => false,
'error' => 'Failed to write file to disk'
];
}
return [
'success' => true,
'filename' => $safeFilename,
'filepath' => $fullPath,
'size' => $bytesWritten,
'asset_id' => $assetId,
'download_url' => "/v6/assets/{$assetId}/content",
'content_type' => $contentType
];
}
// Extract filename and content info for URL-based downloads
$downloadInfo = $this->extractDownloadInfo($assetDetails['data'], $filename);
if (!$downloadInfo['success']) {
return [
'success' => false,
'error' => 'Could not determine download URL: ' . $downloadInfo['error']
];
}
// Download the actual file content
return $this->downloadFile($downloadInfo['url'], $downloadInfo['filename'], $assetId);
}
private function getAssetDetails($assetId)
{
// Try multiple endpoints to get asset content info
$endpoints = [
"/v6/assets/{$assetId}/contents", // CORRECT content endpoint from Postman
"/v6/assets/{$assetId}?load_type=full",
"/v6/assets/{$assetId}?load_type=content",
"/v6/assets/{$assetId}"
];
$debugInfo = [];
foreach ($endpoints as $endpoint) {
$request = [
'method' => 'GET',
'url' => $endpoint
];
$response = $this->apiClient->executeRequest($request);
$debugInfo[] = [
'endpoint' => $endpoint,
'success' => $response['success'],
'http_code' => $response['http_code'] ?? 0,
'error' => $response['error'] ?? null,
'response_size' => strlen($response['body'] ?? '')
];
if ($response['success']) {
$data = json_decode($response['body'], true);
// If this is a direct content response, try to download it directly
if ($endpoint === "/v6/assets/{$assetId}/contents") {
return [
'success' => true,
'direct_content' => true,
'content_data' => $response['body'],
'content_type' => $response['headers']['Content-Type'] ?? 'application/octet-stream',
'debug_info' => $debugInfo
];
}
if ($data) {
return [
'success' => true,
'data' => $data,
'debug_info' => $debugInfo
];
}
}
}
return [
'success' => false,
'error' => 'Could not retrieve asset details from any endpoint',
'debug_info' => $debugInfo
];
}
private function extractDownloadInfo($assetData, $preferredFilename = null)
{
$filename = $preferredFilename;
$downloadUrl = null;
// Try to extract filename from various fields
if (!$filename) {
$filename = $assetData['name'] ??
$assetData['asset_content_info']['master_content']['content_file_name'] ??
$assetData['master_content_info']['content_file_name'] ??
'asset_' . ($assetData['asset_id'] ?? 'unknown');
}
// Try to find download URL from various content sources - prioritize signed URLs
$urlFields = [
// Signed URLs (most reliable)
'delivery_service_url',
'progressive_download_url',
'content_signed_url',
// Content info URLs
'asset_content_info.master_content.content_signed_url',
'asset_content_info.master_content.url',
'master_content_info.content_signed_url',
'master_content_info.url',
// Rendition URLs
'rendition_content.preview_content.content_signed_url',
'rendition_content.preview_content.url',
'rendition_content.thumbnail_content.content_signed_url',
'rendition_content.thumbnail_content.url',
// Generic URLs
'url',
'content_url'
];
foreach ($urlFields as $field) {
$url = $this->getNestedValue($assetData, $field);
if (!empty($url) && $url !== 'string') { // Filter out Postman placeholder values
$downloadUrl = $url;
break;
}
}
if (!$downloadUrl) {
return [
'success' => false,
'error' => 'No download URL found in asset data',
'available_fields' => array_keys($assetData),
'debug_data' => $assetData
];
}
return [
'success' => true,
'url' => $downloadUrl,
'filename' => $filename
];
}
private function getNestedValue($array, $path)
{
$keys = explode('.', $path);
$current = $array;
foreach ($keys as $key) {
if (is_array($current) && isset($current[$key])) {
$current = $current[$key];
} else {
return null;
}
}
return $current;
}
private function downloadFile($url, $filename, $assetId)
{
// Ensure filename has extension
if (!pathinfo($filename, PATHINFO_EXTENSION)) {
$filename .= '.jpg'; // Default for images
}
// Create safe filename
$safeFilename = $this->sanitizeFilename($filename);
$fullPath = $this->downloadPath . $safeFilename;
// Make the download request
$request = [
'method' => 'GET',
'url' => $url
];
$response = $this->apiClient->executeRequest($request);
if (!$response['success']) {
return [
'success' => false,
'error' => 'Download request failed: ' . ($response['error'] ?? 'Unknown error'),
'http_code' => $response['http_code'] ?? 0
];
}
// Save file to disk
$bytesWritten = file_put_contents($fullPath, $response['body']);
if ($bytesWritten === false) {
return [
'success' => false,
'error' => 'Failed to write file to disk'
];
}
return [
'success' => true,
'filename' => $safeFilename,
'filepath' => $fullPath,
'size' => $bytesWritten,
'asset_id' => $assetId,
'download_url' => $url
];
}
private function sanitizeFilename($filename)
{
// Remove dangerous characters and limit length
$safe = preg_replace('/[^a-zA-Z0-9._-]/', '_', $filename);
$safe = substr($safe, 0, 200); // Limit length
// Ensure it doesn't start with a dot
if (strpos($safe, '.') === 0) {
$safe = 'file_' . $safe;
}
return $safe;
}
public function downloadMultipleAssets($assetIds, $folderName = 'bulk_download')
{
$results = [];
$successCount = 0;
$totalSize = 0;
// Create subfolder for this download batch
$batchPath = $this->downloadPath . $this->sanitizeFilename($folderName) . '/';
if (!is_dir($batchPath)) {
mkdir($batchPath, 0755, true);
}
$originalPath = $this->downloadPath;
$this->downloadPath = $batchPath;
foreach ($assetIds as $assetId) {
$result = $this->downloadAsset($assetId);
$results[] = $result;
if ($result['success']) {
$successCount++;
$totalSize += $result['size'];
}
}
$this->downloadPath = $originalPath; // Restore original path
return [
'success' => $successCount > 0,
'total_assets' => count($assetIds),
'successful_downloads' => $successCount,
'failed_downloads' => count($assetIds) - $successCount,
'total_size' => $totalSize,
'download_folder' => $batchPath,
'individual_results' => $results
];
}
public function getDownloadPath()
{
return $this->downloadPath;
}
}