up
This commit is contained in:
@@ -69,7 +69,10 @@ try {
|
||||
'direction' => $msg['direction'],
|
||||
'message_type' => $msg['message_type'],
|
||||
'content' => $msg['content'] ?? '',
|
||||
'media_url' => (isset($msg['media_url']) && $msg['media_url']) ? (preg_match('#^https?://#i', $msg['media_url']) ? $msg['media_url'] : ("api/get_media.php?id=" . urlencode($msg['media_url']))) : null,
|
||||
'media_url' => null,
|
||||
'local_file' => $msg['local_file'] ?? null,
|
||||
'local_thumb' => $msg['local_thumb'] ?? null,
|
||||
'media_url_external' => (isset($msg['media_url']) && $msg['media_url']) ? (preg_match('#^https?://#i', $msg['media_url']) ? $msg['media_url'] : ("api/get_media.php?id=" . urlencode($msg['media_url']))) : null,
|
||||
'filename' => $msg['filename'] ?? null,
|
||||
'mime_type' => $msg['mime_type'] ?? null,
|
||||
'status' => $msg['status'] ?? 'sent',
|
||||
|
||||
@@ -86,7 +86,10 @@ try {
|
||||
'user_id' => intval($msg['user_id']),
|
||||
'content' => $msg['content'] ?? '',
|
||||
'message_id' => $msg['message_id'] ?? null,
|
||||
'media_url' => (isset($msg['media_url']) && $msg['media_url']) ? (preg_match('#^https?://#i', $msg['media_url']) ? $msg['media_url'] : ("api/get_media.php?id=" . urlencode($msg['media_url']))) : null,
|
||||
'media_url' => null,
|
||||
'local_file' => $msg['local_file'] ?? null,
|
||||
'local_thumb' => $msg['local_thumb'] ?? null,
|
||||
'media_url_external' => (isset($msg['media_url']) && $msg['media_url']) ? (preg_match('#^https?://#i', $msg['media_url']) ? $msg['media_url'] : ("api/get_media.php?id=" . urlencode($msg['media_url']))) : null,
|
||||
'filename' => $msg['filename'] ?? null,
|
||||
'mime_type' => $msg['mime_type'] ?? null,
|
||||
'user_phone' => $msg['user_phone'] ?? null,
|
||||
|
||||
@@ -70,6 +70,49 @@ if ($providedUrl) {
|
||||
}
|
||||
}
|
||||
|
||||
// Soporte para archivos locales (local=uploads/media/...)
|
||||
$local = $_GET['local'] ?? null;
|
||||
if ($local) {
|
||||
$localPath = realpath(__DIR__ . '/../../' . ltrim($local, '/'));
|
||||
$base = realpath(__DIR__ . '/../../uploads/media');
|
||||
// Prevención simple de path traversal
|
||||
if (!$localPath || strpos($localPath, $base) !== 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Ruta local inválida']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!file_exists($localPath)) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'error' => 'Archivo no encontrado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($wantsJson) {
|
||||
// devolver URL pública directa (relative)
|
||||
echo json_encode(['success' => true, 'url' => '/' . ltrim($local, '/')]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($download) {
|
||||
// Forzar descarga del archivo local
|
||||
header('Cache-Control: public, max-age=3600');
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$ctype = finfo_file($finfo, $localPath);
|
||||
finfo_close($finfo);
|
||||
header('Content-Type: ' . $ctype);
|
||||
header('Content-Length: ' . filesize($localPath));
|
||||
header('Content-Disposition: attachment; filename="' . basename($localPath) . '"');
|
||||
readfile($localPath);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Redirigir al archivo local (servidor web lo servirá)
|
||||
header('Cache-Control: public, max-age=3600');
|
||||
header('Location: /' . ltrim($local, '/'), true, 302);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Si se solicitó JSON, devolver la URL
|
||||
if ($wantsJson) {
|
||||
echo json_encode(['success' => true, 'url' => $mediaUrl]);
|
||||
@@ -79,11 +122,17 @@ if ($wantsJson) {
|
||||
// Si se pidió proxy (download=1), traer el contenido y retornarlo como descarga
|
||||
if ($download) {
|
||||
$ch = curl_init();
|
||||
// Añadir Authorization si existe token en configuración
|
||||
$token = getConfigFromDB('whatsapp_token', '');
|
||||
$headers = ['User-Agent: WhatsAppMediaProxy/1.0', 'Accept: */*'];
|
||||
if ($token) $headers[] = 'Authorization: Bearer ' . $token;
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $mediaUrl,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 60,
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
]);
|
||||
$content = curl_exec($ch);
|
||||
$info = curl_getinfo($ch);
|
||||
|
||||
@@ -164,23 +164,103 @@ class WhatsAppWebhook {
|
||||
$mediaUrl = $message['image']['url'] ?? ($message['image']['id'] ?? null);
|
||||
$mimeType = $message['image']['mime_type'] ?? null;
|
||||
$filename = $message['image']['filename'] ?? null;
|
||||
|
||||
// Intentar descargar y almacenar imagen localmente
|
||||
try {
|
||||
$ms = new MediaService();
|
||||
$res = null;
|
||||
if ($mediaUrl && preg_match('#^https?://#i', $mediaUrl)) {
|
||||
// Si venía URL directa, descargar desde URL
|
||||
$content = $ms->downloadUrl($mediaUrl);
|
||||
if ($content !== false) {
|
||||
$subdir = date('Y/m');
|
||||
$dir = 'uploads/media/' . $subdir;
|
||||
if (!is_dir(__DIR__ . '/../' . $dir)) mkdir(__DIR__ . '/../' . $dir, 0755, true);
|
||||
$ext = $ms->extensionFromMime($mimeType) ?: pathinfo(parse_url($mediaUrl, PHP_URL_PATH), PATHINFO_EXTENSION) ?: 'jpg';
|
||||
$fname = uniqid('m_') . '.' . $ext;
|
||||
$path = __DIR__ . '/../' . $dir . '/' . $fname;
|
||||
file_put_contents($path, $content);
|
||||
$res = ['local_file' => $dir . '/' . $fname];
|
||||
// crear thumb
|
||||
if ($ms->isImageMime($mimeType)) {
|
||||
$thumbPath = __DIR__ . '/../' . $dir . '/thumb_' . $fname . '.jpg';
|
||||
if ($ms->createThumbnail($path, $thumbPath)) {
|
||||
$res['local_thumb'] = $dir . '/thumb_' . $fname . '.jpg';
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($mediaUrl) {
|
||||
// mediaUrl probablemente es media id -> usar fetchAndStoreFromGraph
|
||||
$res = $ms->fetchAndStoreFromGraph($mediaUrl, date('Y/m'));
|
||||
}
|
||||
if ($res) {
|
||||
$localFile = $res['local_file'] ?? null;
|
||||
$localThumb = $res['local_thumb'] ?? null;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log('Media download failed: ' . $e->getMessage());
|
||||
// Encolar para reintento si tenemos un media id (no descargar documentos aquí)
|
||||
try {
|
||||
if ($mediaUrl && !preg_match('#^https?://#i', $mediaUrl)) {
|
||||
$this->db->insert('media_queue', [
|
||||
'media_id' => $mediaUrl,
|
||||
'subdir' => date('Y/m'),
|
||||
'status' => 'pending',
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
} catch (Exception $e2) {
|
||||
error_log('Failed to enqueue media download: ' . $e2->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
} elseif (isset($message['audio'])) {
|
||||
$messageType = 'audio';
|
||||
$mediaUrl = $message['audio']['url'] ?? ($message['audio']['id'] ?? null);
|
||||
$mimeType = $message['audio']['mime_type'] ?? null;
|
||||
$filename = $message['audio']['filename'] ?? null;
|
||||
|
||||
// Descargar audio y guardar local (opcional)
|
||||
try {
|
||||
$ms = new MediaService();
|
||||
if ($mediaUrl && !preg_match('#^https?://#i', $mediaUrl)) {
|
||||
$res = $ms->fetchAndStoreFromGraph($mediaUrl, date('Y/m'));
|
||||
if ($res) {
|
||||
$localFile = $res['local_file'] ?? null;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log('Audio download failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
} elseif (isset($message['video'])) {
|
||||
$messageText = $message['video']['caption'] ?? '';
|
||||
$messageType = 'video';
|
||||
$mediaUrl = $message['video']['url'] ?? ($message['video']['id'] ?? null);
|
||||
$mimeType = $message['video']['mime_type'] ?? null;
|
||||
$filename = $message['video']['filename'] ?? null;
|
||||
|
||||
// Descargar video y guardar local (opcional)
|
||||
try {
|
||||
$ms = new MediaService();
|
||||
if ($mediaUrl && !preg_match('#^https?://#i', $mediaUrl)) {
|
||||
$res = $ms->fetchAndStoreFromGraph($mediaUrl, date('Y/m'));
|
||||
if ($res) {
|
||||
$localFile = $res['local_file'] ?? null;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log('Video download failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
} elseif (isset($message['document'])) {
|
||||
$messageText = $message['document']['filename'] ?? '';
|
||||
$messageType = 'document';
|
||||
$mediaUrl = $message['document']['url'] ?? ($message['document']['id'] ?? null);
|
||||
$mimeType = $message['document']['mime_type'] ?? null;
|
||||
$filename = $message['document']['filename'] ?? null;
|
||||
|
||||
// Do NOT download documents automatically; keep media id so browser will redirect and download
|
||||
}
|
||||
|
||||
// Log debug (temporal) para verificar cómo llegan media_url/filename
|
||||
@@ -205,6 +285,13 @@ class WhatsAppWebhook {
|
||||
if (isset($mimeType) && $mimeType !== null) {
|
||||
$saveData['mime_type'] = $mimeType;
|
||||
}
|
||||
// Si se descargó y almacenó localmente, añadir las rutas locales
|
||||
if (isset($localFile) && $localFile) {
|
||||
$saveData['local_file'] = $localFile;
|
||||
}
|
||||
if (isset($localThumb) && $localThumb) {
|
||||
$saveData['local_thumb'] = $localThumb;
|
||||
}
|
||||
|
||||
// Si el mensaje incluye contexto (es respuesta a otro mensaje)
|
||||
if (isset($message['context']) && isset($message['context']['id'])) {
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* Simple MediaService
|
||||
* - fetchFromGraph(mediaId) -> gets Graph API URL and downloads
|
||||
* - storeContent($bytes, $destPath)
|
||||
* - createThumbnail($srcPath, $thumbPath)
|
||||
*/
|
||||
class MediaService {
|
||||
private $token;
|
||||
private $apiUrl;
|
||||
private $uploadsDir;
|
||||
|
||||
public function __construct() {
|
||||
$this->token = getConfigFromDB('whatsapp_token', '');
|
||||
$this->apiUrl = rtrim(getConfigFromDB('whatsapp_api_url', 'https://graph.facebook.com/v22.0/'), '/');
|
||||
$this->uploadsDir = __DIR__ . '/../uploads/media';
|
||||
if (!is_dir($this->uploadsDir)) mkdir($this->uploadsDir, 0755, true);
|
||||
}
|
||||
|
||||
public function fetchAndStoreFromGraph($mediaId, $subdir = '') {
|
||||
// Obtener metadata (url)
|
||||
$endpoint = "{$this->apiUrl}/{$mediaId}";
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $endpoint,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $this->token],
|
||||
CURLOPT_TIMEOUT => 15
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($err || $http >= 400) {
|
||||
throw new Exception('Graph API fetch failed: ' . $err);
|
||||
}
|
||||
$decoded = json_decode($resp, true);
|
||||
$mediaUrl = $decoded['url'] ?? ($decoded['data'][0]['url'] ?? null);
|
||||
if (!$mediaUrl) throw new Exception('No media url from Graph');
|
||||
|
||||
// Descargar contenido (primer intento)
|
||||
$content = $this->downloadUrl($mediaUrl);
|
||||
if ($content === false) {
|
||||
// Reintentar con header Referer (algunos endpoints requieren referer)
|
||||
$content = $this->downloadUrl($mediaUrl, ['Referer: https://graph.facebook.com']);
|
||||
}
|
||||
if ($content === false) throw new Exception('Failed to download media content from ' . $mediaUrl);
|
||||
|
||||
// Detectar extension desde mime o url
|
||||
$mime = $decoded['mime_type'] ?? null;
|
||||
$ext = $this->extensionFromMime($mime) ?: pathinfo(parse_url($mediaUrl, PHP_URL_PATH), PATHINFO_EXTENSION) ?: 'bin';
|
||||
|
||||
$dir = $this->uploadsDir . ($subdir ? '/' . trim($subdir, '/') : '');
|
||||
if (!is_dir($dir)) mkdir($dir, 0755, true);
|
||||
|
||||
$filename = uniqid('m_') . '.' . $ext;
|
||||
$fullPath = $dir . '/' . $filename;
|
||||
file_put_contents($fullPath, $content);
|
||||
|
||||
$result = ['local_file' => 'uploads/media' . ($subdir ? '/' . trim($subdir, '/') : '') . '/' . $filename, 'mime_type' => $mime, 'size' => filesize($fullPath)];
|
||||
|
||||
// Si es imagen, generar miniatura
|
||||
if ($this->isImageMime($mime) || preg_match('#^jpe?g|png|gif$#i', $ext)) {
|
||||
$thumbPath = $dir . '/thumb_' . $filename . '.jpg';
|
||||
$thumbRel = dirname($result['local_file']) . '/thumb_' . $filename . '.jpg';
|
||||
if ($this->createThumbnail($fullPath, $thumbPath)) {
|
||||
$result['local_thumb'] = $thumbRel;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function downloadUrl($url, $extraHeaders = []) {
|
||||
// Incluir Authorization Bearer si está configurado (necesario para algunos endpoints)
|
||||
$authHeader = [];
|
||||
if (!empty($this->token)) {
|
||||
$authHeader[] = 'Authorization: Bearer ' . $this->token;
|
||||
}
|
||||
$headers = array_merge(['User-Agent: WhatsAppMediaFetcher/1.0', 'Accept: */*'], $authHeader, $extraHeaders);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
]);
|
||||
$content = curl_exec($ch);
|
||||
$err = curl_error($ch);
|
||||
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($err || $http >= 400) return false;
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function extensionFromMime($mime) {
|
||||
if (!$mime) return null;
|
||||
$map = [
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'audio/mpeg' => 'mp3',
|
||||
'audio/ogg' => 'ogg',
|
||||
'video/mp4' => 'mp4',
|
||||
'text/csv' => 'csv',
|
||||
'application/pdf' => 'pdf'
|
||||
];
|
||||
return $map[$mime] ?? null;
|
||||
}
|
||||
|
||||
public function isImageMime($mime) {
|
||||
return strpos($mime, 'image/') === 0;
|
||||
}
|
||||
|
||||
public function createThumbnail($src, $dest, $maxW = 400, $maxH = 400) {
|
||||
if (!function_exists('getimagesize')) return false;
|
||||
$info = @getimagesize($src);
|
||||
if (!$info) return false;
|
||||
list($w, $h) = $info;
|
||||
$mime = $info['mime'];
|
||||
|
||||
switch ($mime) {
|
||||
case 'image/jpeg': $srcImg = imagecreatefromjpeg($src); break;
|
||||
case 'image/png': $srcImg = imagecreatefrompng($src); break;
|
||||
case 'image/gif': $srcImg = imagecreatefromgif($src); break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
$ratio = min($maxW / $w, $maxH / $h, 1);
|
||||
$nw = (int)($w * $ratio);
|
||||
$nh = (int)($h * $ratio);
|
||||
|
||||
$thumb = imagecreatetruecolor($nw, $nh);
|
||||
imagecopyresampled($thumb, $srcImg, 0,0,0,0, $nw, $nh, $w, $h);
|
||||
imagejpeg($thumb, $dest, 85);
|
||||
imagedestroy($thumb);
|
||||
imagedestroy($srcImg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+13
-5
@@ -967,7 +967,8 @@
|
||||
const statusIcon = this.getStatusIcon(msg.status);
|
||||
|
||||
// Renderizar contenido (texto o multimedia)
|
||||
const content = msg.media_url
|
||||
// Preferir miniatura local para previsualización si existe
|
||||
const content = (msg.local_thumb || msg.local_file || msg.media_url_external)
|
||||
? this.renderMediaMessage(msg)
|
||||
: (msg.content || msg.message_text || '[Mensaje vacío]');
|
||||
|
||||
@@ -1354,18 +1355,22 @@
|
||||
|
||||
switch (mediaType) {
|
||||
case 'image':
|
||||
// Usar miniatura local si existe, al abrir expandir con el archivo local completo o con media-url redirect
|
||||
const thumb = msg.local_thumb ? (`/${msg.local_thumb}`) : (msg.local_file ? (`/${msg.local_file}`) : (msg.media_url_external || mediaUrl));
|
||||
const full = msg.local_file ? (`/api/version/media-url.php?local=${encodeURIComponent(msg.local_file)}`) : (`/api/version/media-url.php?id=${encodeURIComponent(msg.media_url || '')}`);
|
||||
return `
|
||||
<div class="message-media">
|
||||
<img src="${mediaUrl}" alt="Imagen" onclick="window.open('${mediaUrl}', '_blank')">
|
||||
<img src="${thumb}" alt="Imagen" onclick="window.open('${full}', '_blank')" style="cursor:zoom-in">
|
||||
</div>
|
||||
${caption ? `<div>${caption}</div>` : ''}
|
||||
`;
|
||||
|
||||
case 'video':
|
||||
const videoSrc = msg.local_file ? (`/${msg.local_file}`) : (msg.media_url_external || mediaUrl);
|
||||
return `
|
||||
<div class="message-media">
|
||||
<video controls>
|
||||
<source src="${mediaUrl}" type="video/mp4">
|
||||
<source src="${videoSrc}" type="video/mp4">
|
||||
Tu navegador no soporta video.
|
||||
</video>
|
||||
</div>
|
||||
@@ -1373,18 +1378,21 @@
|
||||
`;
|
||||
|
||||
case 'audio':
|
||||
const audioSrc = msg.local_file ? (`/${msg.local_file}`) : (msg.media_url_external || mediaUrl);
|
||||
return `
|
||||
<div class="message-media">
|
||||
<audio controls>
|
||||
<source src="${mediaUrl}" type="audio/mpeg">
|
||||
<source src="${audioSrc}" type="audio/mpeg">
|
||||
Tu navegador no soporta audio.
|
||||
</audio>
|
||||
</div>
|
||||
`;
|
||||
|
||||
case 'document':
|
||||
// Para documentos no descargamos automáticamente; abrir la URL externa (o la redirección por media id) para descargar
|
||||
const docUrl = msg.local_file ? (`/api/version/media-url.php?local=${encodeURIComponent(msg.local_file)}&download=1`) : (`/api/version/media-url.php?id=${encodeURIComponent(msg.media_url || msg.media_url_external || '')}&download=1`);
|
||||
return `
|
||||
<div class="message-document" onclick="window.open('${mediaUrl}', '_blank')">
|
||||
<div class="message-document" onclick="window.open('${docUrl}', '_blank')" style="cursor:pointer">
|
||||
<i class="fas fa-file-pdf"></i>
|
||||
<div class="document-info">
|
||||
<div class="document-name">${caption || 'Documento'}</div>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Migration: Add local media columns to conversations
|
||||
ALTER TABLE conversations
|
||||
ADD COLUMN local_file VARCHAR(255) NULL AFTER media_url,
|
||||
ADD COLUMN local_thumb VARCHAR(255) NULL AFTER local_file,
|
||||
ADD COLUMN media_storage VARCHAR(50) NULL AFTER local_thumb;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Migration: create media_queue table
|
||||
CREATE TABLE IF NOT EXISTS media_queue (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
media_id VARCHAR(255) NOT NULL,
|
||||
subdir VARCHAR(255) DEFAULT NULL,
|
||||
status ENUM('pending','done','failed') DEFAULT 'pending',
|
||||
result TEXT DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* Worker simple para reintentar descargas de media fallidas (opcional)
|
||||
* Revisa la tabla media_queue (si existe) e intenta procesar
|
||||
*/
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/../classes/MediaService.php';
|
||||
$db = Database::getInstance();
|
||||
$rows = $db->fetchAll('SELECT * FROM media_queue WHERE status = ? ORDER BY created_at ASC LIMIT 10', ['pending']);
|
||||
$ms = new MediaService();
|
||||
foreach ($rows as $r) {
|
||||
try {
|
||||
$res = $ms->fetchAndStoreFromGraph($r['media_id'], $r['subdir'] ?? date('Y/m'));
|
||||
if ($res) {
|
||||
$db->update('media_queue', ['status' => 'done', 'result' => json_encode($res)], 'id = :id', ['id' => $r['id']]);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$db->update('media_queue', ['status' => 'failed', 'result' => json_encode(['error' => $e->getMessage()])], 'id = :id', ['id' => $r['id']]);
|
||||
}
|
||||
}
|
||||
echo "Processed " . count($rows) . " jobs\n";
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
$db = Database::getInstance();
|
||||
$dir = __DIR__ . '/../migrations';
|
||||
$files = glob($dir . '/*.sql');
|
||||
foreach ($files as $f) {
|
||||
echo "Running migration: " . basename($f) . "\n";
|
||||
$sql = file_get_contents($f);
|
||||
try {
|
||||
$db->execute($sql);
|
||||
echo "OK\n";
|
||||
} catch (Exception $e) {
|
||||
echo "Failed: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
echo "Migrations completed.\n";
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/../classes/MediaService.php';
|
||||
$mediaId = $argv[1] ?? null;
|
||||
if (!$mediaId) { echo "Usage: php test_media_store.php <mediaId>\n"; exit(1); }
|
||||
$ms = new MediaService();
|
||||
try {
|
||||
$res = $ms->fetchAndStoreFromGraph($mediaId, date('Y/m'));
|
||||
echo json_encode($res, JSON_PRETTY_PRINT) . "\n";
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
Reference in New Issue
Block a user