Files
MokoSuiteOpenGraph/source/packages/com_mokoog/src/Controller/ImportExportController.php
T

256 lines
8.6 KiB
PHP
Raw Normal View History

<?php
/**
* @package MokoSuiteOpenGraph
* @subpackage com_mokoog
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Joomla\Component\MokoOG\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Session\Session;
class ImportExportController extends BaseController
{
/**
* Maximum upload file size in bytes (2 MB).
*/
private const MAX_FILE_SIZE = 2 * 1024 * 1024;
/**
* Allowed content_type patterns for import.
*/
private const CONTENT_TYPE_PATTERN = '/^[a-z][a-z0-9_.]*$/';
/**
* Export all OG tags as CSV.
*
* @return void
*/
public function export(): void
{
Session::checkToken('get') || jexit(Text::_('JINVALID_TOKEN'));
if (!Factory::getApplication()->getIdentity()->authorise('core.manage', 'com_mokoog')) {
throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
}
$app = Factory::getApplication();
$db = Factory::getDbo();
// Join with #__content to get article titles for reference
$query = $db->getQuery(true)
->select([
$db->quoteName('t.content_type'),
$db->quoteName('t.content_id'),
'COALESCE(' . $db->quoteName('c.title') . ', ' . $db->quote('') . ') AS ' . $db->quoteName('article_title'),
$db->quoteName('t.og_title'),
$db->quoteName('t.og_description'),
$db->quoteName('t.og_image'),
$db->quoteName('t.og_type'),
$db->quoteName('t.seo_title'),
$db->quoteName('t.meta_description'),
$db->quoteName('t.robots'),
$db->quoteName('t.canonical_url'),
$db->quoteName('t.language'),
])
->from($db->quoteName('#__mokoog_tags', 't'))
->leftJoin(
$db->quoteName('#__content', 'c')
. ' ON ' . $db->quoteName('t.content_type') . ' = ' . $db->quote('com_content')
. ' AND ' . $db->quoteName('t.content_id') . ' = ' . $db->quoteName('c.id')
)
->order($db->quoteName('t.content_type') . ', ' . $db->quoteName('t.content_id'));
$db->setQuery($query);
$rows = $db->loadAssocList();
// Send CSV headers
$app->setHeader('Content-Type', 'text/csv; charset=utf-8');
$app->setHeader('Content-Disposition', 'attachment; filename="mokoog_tags_export.csv"');
$app->sendHeaders();
$output = fopen('php://output', 'w');
// Header row
fputcsv($output, [
'content_type', 'content_id', 'article_title',
'og_title', 'og_description', 'og_image', 'og_type',
'seo_title', 'meta_description', 'robots', 'canonical_url',
'language',
]);
foreach ($rows as $row) {
fputcsv($output, $row);
}
fclose($output);
$app->close();
}
/**
* Import OG tags from uploaded CSV.
*
* @return void
*/
public function import(): void
{
Session::checkToken() || jexit(Text::_('JINVALID_TOKEN'));
$identity = Factory::getApplication()->getIdentity();
if (!$identity->authorise('core.create', 'com_mokoog') || !$identity->authorise('core.edit', 'com_mokoog')) {
throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
}
$app = Factory::getApplication();
$input = $app->getInput();
$files = $input->files->get('jform', [], 'array');
if (empty($files['csv_file']['tmp_name'])) {
$app->enqueueMessage(Text::_('COM_MOKOOG_IMPORT_NO_FILE'), 'error');
$app->redirect('index.php?option=com_mokoog&view=tags');
return;
}
$csvFile = $files['csv_file'];
// Validate file extension
$ext = strtolower(pathinfo($csvFile['name'] ?? '', PATHINFO_EXTENSION));
if ($ext !== 'csv') {
$app->enqueueMessage(Text::_('COM_MOKOOG_IMPORT_INVALID_TYPE'), 'error');
$app->redirect('index.php?option=com_mokoog&view=tags');
return;
}
// Validate MIME type
$allowedMimes = ['text/csv', 'text/plain', 'application/csv', 'application/vnd.ms-excel'];
if (!empty($csvFile['type']) && !\in_array($csvFile['type'], $allowedMimes, true)) {
$app->enqueueMessage(Text::_('COM_MOKOOG_IMPORT_INVALID_TYPE'), 'error');
$app->redirect('index.php?option=com_mokoog&view=tags');
return;
}
// Validate file size
if (($csvFile['size'] ?? 0) > self::MAX_FILE_SIZE) {
$app->enqueueMessage(Text::sprintf('COM_MOKOOG_IMPORT_FILE_TOO_LARGE', '2 MB'), 'error');
$app->redirect('index.php?option=com_mokoog&view=tags');
return;
}
$tmpFile = $csvFile['tmp_name'];
$handle = fopen($tmpFile, 'r');
if (!$handle) {
$app->enqueueMessage(Text::_('COM_MOKOOG_IMPORT_READ_ERROR'), 'error');
$app->redirect('index.php?option=com_mokoog&view=tags');
return;
}
$db = Factory::getDbo();
$header = fgetcsv($handle);
$created = 0;
$updated = 0;
$skipped = 0;
$now = Factory::getDate()->toSql();
while (($row = fgetcsv($handle)) !== false) {
if (\count($row) < 7) {
$skipped++;
continue;
}
$contentType = trim($row[0]);
$contentId = (int) $row[1];
// $row[2] = article_title (informational, skip)
$ogTitle = trim($row[3] ?? '');
$ogDescription = trim($row[4] ?? '');
$ogImage = trim($row[5] ?? '');
$ogType = trim($row[6] ?? 'article');
$seoTitle = trim($row[7] ?? '');
$metaDesc = trim($row[8] ?? '');
$robots = trim($row[9] ?? '');
$canonicalUrl = trim($row[10] ?? '');
$language = trim($row[11] ?? '*');
// Validate language tag format (e.g., 'en-GB', '*')
if ($language !== '*' && !preg_match('/^[a-z]{2,3}-[A-Z]{2}$/', $language)) {
$language = '*';
}
if (empty($contentType) || $contentId <= 0) {
$skipped++;
continue;
}
// Validate content_type against allowed pattern
if (!preg_match(self::CONTENT_TYPE_PATTERN, $contentType)) {
$skipped++;
continue;
}
// Check for existing record (unique key includes language)
$query = $db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__mokoog_tags'))
->where($db->quoteName('content_type') . ' = ' . $db->quote($contentType))
->where($db->quoteName('content_id') . ' = ' . $contentId)
->where($db->quoteName('language') . ' = ' . $db->quote($language));
$db->setQuery($query);
$existingId = $db->loadResult();
$record = (object) [
'content_type' => $contentType,
'content_id' => $contentId,
'og_title' => $ogTitle,
'og_description' => $ogDescription,
'og_image' => $ogImage,
'og_type' => $ogType,
'seo_title' => $seoTitle,
'meta_description' => $metaDesc,
'robots' => $robots,
'canonical_url' => $canonicalUrl,
'language' => $language,
'published' => 1,
'modified' => $now,
];
if ($existingId) {
$record->id = $existingId;
$db->updateObject('#__mokoog_tags', $record, 'id');
$updated++;
} else {
$record->created = $now;
$db->insertObject('#__mokoog_tags', $record);
$created++;
}
}
fclose($handle);
$app->enqueueMessage(
Text::sprintf('COM_MOKOOG_IMPORT_RESULT', $created, $updated, $skipped),
'success'
);
$app->redirect('index.php?option=com_mokoog&view=tags');
}
}