Merge branch 'feature/12-csv-import-export' into dev

This commit is contained in:
Jonathan Miller
2026-05-23 22:08:06 -05:00
3 changed files with 200 additions and 0 deletions
@@ -49,3 +49,9 @@ COM_MOKOOG_BATCH_FOUND="articles found without OG tags."
COM_MOKOOG_BATCH_PROCESSED="processed"
COM_MOKOOG_BATCH_COMPLETE="Batch generation complete!"
COM_MOKOOG_BATCH_ERROR="Error:"
COM_MOKOOG_TOOLBAR_EXPORT="Export CSV"
COM_MOKOOG_TOOLBAR_IMPORT="Import CSV"
COM_MOKOOG_IMPORT_NO_FILE="No CSV file was uploaded."
COM_MOKOOG_IMPORT_READ_ERROR="Could not read the uploaded CSV file."
COM_MOKOOG_IMPORT_RESULT="Import complete: %d created, %d updated, %d skipped."
@@ -49,3 +49,9 @@ COM_MOKOOG_BATCH_FOUND="articles found without OG tags."
COM_MOKOOG_BATCH_PROCESSED="processed"
COM_MOKOOG_BATCH_COMPLETE="Batch generation complete!"
COM_MOKOOG_BATCH_ERROR="Error:"
COM_MOKOOG_TOOLBAR_EXPORT="Export CSV"
COM_MOKOOG_TOOLBAR_IMPORT="Import CSV"
COM_MOKOOG_IMPORT_NO_FILE="No CSV file was uploaded."
COM_MOKOOG_IMPORT_READ_ERROR="Could not read the uploaded CSV file."
COM_MOKOOG_IMPORT_RESULT="Import complete: %d created, %d updated, %d skipped."
@@ -0,0 +1,188 @@
<?php
/**
* @package MokoOpenGraph
* @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
{
/**
* Export all OG tags as CSV.
*
* @return void
*/
public function export(): void
{
Session::checkToken('get') || jexit(Text::_('JINVALID_TOKEN'));
$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'),
])
->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',
]);
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'));
$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;
}
$tmpFile = $files['csv_file']['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] ?? '');
if (empty($contentType) || $contentId <= 0) {
$skipped++;
continue;
}
// Check for existing record
$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);
$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,
'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');
}
}