diff --git a/public/ajax/save_monthly_preview_to_joomla.php b/public/ajax/save_monthly_preview_to_joomla.php new file mode 100644 index 0000000..4f82ca2 --- /dev/null +++ b/public/ajax/save_monthly_preview_to_joomla.php @@ -0,0 +1,267 @@ + false, + 'message' => 'Nur POST ist erlaubt.', + ]); +} + +if (!currentUserIsAdminLike()) { + respondWithJoomlaPreviewResult(403, [ + 'success' => false, + 'message' => 'Für den Joomla-Export sind Administratorrechte erforderlich.', + ]); +} + +$sessionToken = isset($_SESSION['joomla_monthly_preview_token']) && is_string($_SESSION['joomla_monthly_preview_token']) + ? $_SESSION['joomla_monthly_preview_token'] + : ''; +$submittedToken = isset($_POST['joomla_monthly_preview_token']) ? (string) $_POST['joomla_monthly_preview_token'] : ''; + +if ($sessionToken === '' || $submittedToken === '' || !hash_equals($sessionToken, $submittedToken)) { + respondWithJoomlaPreviewResult(403, [ + 'success' => false, + 'message' => 'Sicherheitsprüfung fehlgeschlagen. Bitte die Seite neu laden und erneut versuchen.', + ]); +} + +$year = filter_input(INPUT_POST, 'year', FILTER_VALIDATE_INT); +$month = filter_input(INPUT_POST, 'month', FILTER_VALIDATE_INT); +$html = isset($_POST['html']) ? (string) $_POST['html'] : ''; + +if ($year === false || $year < 2000 || $year > 2100 || $month === false || $month < 1 || $month > 12) { + respondWithJoomlaPreviewResult(400, [ + 'success' => false, + 'message' => 'Ungültiger Monat für den Joomla-Export.', + ]); +} + +if ($html === '' || strlen($html) > 14 * 1024 * 1024) { + respondWithJoomlaPreviewResult(400, [ + 'success' => false, + 'message' => 'Der HTML-Inhalt fehlt oder ist zu groß.', + ]); +} + +$monthNames = [ + 1 => 'Januar', 2 => 'Februar', 3 => 'März', 4 => 'April', + 5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August', + 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember', +]; +$monthName = $monthNames[$month]; +$title = sprintf('Monatsvorschau %s %d', $monthName, $year); +$aliasMonthNames = [ + 1 => 'januar', 2 => 'februar', 3 => 'maerz', 4 => 'april', + 5 => 'mai', 6 => 'juni', 7 => 'juli', 8 => 'august', + 9 => 'september', 10 => 'oktober', 11 => 'november', 12 => 'dezember', +]; +$alias = sprintf('monatsvorschau-%s-%d', $aliasMonthNames[$month], $year); +$visibilityDate = (new DateTimeImmutable(sprintf('%04d-%02d-01 00:00:00', $year, $month), new DateTimeZone('Europe/Berlin'))) + ->modify('-5 days') + ->format('Y-m-d H:i:s'); +$joomlaImagesJson = json_encode([ + 'image_intro' => '', 'image_intro_alt' => '', 'float_intro' => '', 'image_intro_caption' => '', + 'image_fulltext' => '', 'image_fulltext_alt' => '', 'float_fulltext' => '', 'image_fulltext_caption' => '', +], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}'; +$joomlaUrlsJson = json_encode([ + 'urla' => '', 'urlatext' => '', 'targeta' => '', 'urlb' => '', 'urlbtext' => '', 'targetb' => '', + 'urlc' => '', 'urlctext' => '', 'targetc' => '', +], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}'; +$joomlaAttribsJson = json_encode([ + 'article_layout' => '', 'show_title' => '', 'link_titles' => '', 'show_tags' => '', 'show_intro' => '', + 'info_block_position' => '', 'info_block_show_title' => '', 'show_category' => '', 'link_category' => '', + 'show_parent_category' => '', 'link_parent_category' => '', 'show_author' => '', 'link_author' => '', + 'show_create_date' => '', 'show_modify_date' => '', 'show_publish_date' => '', 'show_item_navigation' => '', + 'show_hits' => '', 'show_noauth' => '', 'urls_position' => '', 'alternative_readmore' => '', + 'article_page_title' => '', 'show_publishing_options' => '', 'show_article_options' => '', + 'show_urls_images_backend' => '', 'show_urls_images_frontend' => '', +], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}'; +$joomlaMetadataJson = json_encode([ + 'robots' => '', 'author' => '', 'rights' => '', +], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}'; + +try { + $dbConfig = require __DIR__ . '/../../config/observationtips_database.php'; + $pdo = new PDO( + sprintf( + 'mysql:host=%s;dbname=%s;charset=%s', + $dbConfig['host'], + $dbConfig['dbname'], + $dbConfig['charset'] ?? 'utf8mb4' + ), + $dbConfig['user'], + $dbConfig['pass'], + [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ] + ); + + $pdo->beginTransaction(); + try { + $existingStmt = $pdo->prepare( + 'SELECT `id`, `asset_id`, `created`, `created_by` ' + . 'FROM `astro_content` ' + . 'WHERE `catid` = 59 AND `title` = :title ' + . 'ORDER BY `id` DESC ' + . 'LIMIT 1 ' + . 'FOR UPDATE' + ); + $existingStmt->execute([':title' => $title]); + $existingArticle = $existingStmt->fetch(); + $articleId = 0; + + if (is_array($existingArticle)) { + $updateStmt = $pdo->prepare( + 'UPDATE `astro_content` ' + . 'SET `alias` = :alias, `introtext` = :introtext, `fulltext` = \'\', `state` = 1, `created` = :created, ' + . '`images` = :images, `urls` = :urls, `attribs` = :attribs, `metadata` = :metadata, ' + . '`checked_out` = NULL, `checked_out_time` = NULL, ' + . '`modified` = NOW(), `modified_by` = :modified_by, `publish_up` = :publish_up, ' + . '`publish_down` = NULL, `version` = `version` + 1 ' + . 'WHERE `id` = :id' + ); + $updateStmt->execute([ + ':alias' => $alias, + ':introtext' => $html, + ':created' => $visibilityDate, + ':images' => $joomlaImagesJson, + ':urls' => $joomlaUrlsJson, + ':attribs' => $joomlaAttribsJson, + ':metadata' => $joomlaMetadataJson, + ':modified_by' => (int) $existingArticle['created_by'], + ':publish_up' => $visibilityDate, + ':id' => (int) $existingArticle['id'], + ]); + $articleId = (int) $existingArticle['id']; + + $result = [ + 'success' => true, + 'message' => sprintf('%s wurde in Joomla aktualisiert.', $title), + 'action' => 'updated', + ]; + } else { + $authorStmt = $pdo->query( + 'SELECT `created_by` FROM `astro_content` WHERE `catid` = 59 ORDER BY `id` DESC LIMIT 1' + ); + $authorId = (int) $authorStmt->fetchColumn(); + if ($authorId <= 0) { + throw new RuntimeException('Kein Joomla-Autor für die Monatsvorschau vorhanden.'); + } + + $insertContentStmt = $pdo->prepare( + 'INSERT INTO `astro_content` (' + . '`asset_id`, `title`, `alias`, `introtext`, `fulltext`, `state`, `catid`, `created`, `created_by`, ' + . '`modified`, `modified_by`, `checked_out`, `checked_out_time`, `publish_up`, `publish_down`, ' + . '`images`, `urls`, `attribs`, `version`, `ordering`, `metakey`, `metadesc`, `access`, `hits`, ' + . '`metadata`, `featured`, `language`, `note` ' + . ') VALUES (0, :title, :alias, :introtext, \'\', 1, 59, :created, :created_by, ' + . 'NOW(), :modified_by, NULL, NULL, :publish_up, NULL, :images, :urls, :attribs, 1, 0, \'\', \'\', 1, 0, ' + . ':metadata, 0, \'*\', \'\')' + ); + $insertContentStmt->execute([ + ':title' => $title, + ':alias' => $alias, + ':introtext' => $html, + ':created' => $visibilityDate, + ':created_by' => $authorId, + ':modified_by' => $authorId, + ':publish_up' => $visibilityDate, + ':images' => $joomlaImagesJson, + ':urls' => $joomlaUrlsJson, + ':attribs' => $joomlaAttribsJson, + ':metadata' => $joomlaMetadataJson, + ]); + $articleId = (int) $pdo->lastInsertId(); + + // Joomla speichert Artikelrechte als Knoten im Asset-Baum. + $categoryAssetStmt = $pdo->query( + 'SELECT `id`, `rgt`, `level` FROM `astro_assets` WHERE `id` = 740 FOR UPDATE' + ); + $categoryAsset = $categoryAssetStmt->fetch(); + if (!is_array($categoryAsset)) { + throw new RuntimeException('Die Joomla-Kategorie für Monatsvorschauen wurde nicht gefunden.'); + } + + $newLeft = (int) $categoryAsset['rgt']; + $pdo->prepare('UPDATE `astro_assets` SET `rgt` = `rgt` + 2 WHERE `rgt` >= :position') + ->execute([':position' => $newLeft]); + $pdo->prepare('UPDATE `astro_assets` SET `lft` = `lft` + 2 WHERE `lft` > :position') + ->execute([':position' => $newLeft]); + + $insertAssetStmt = $pdo->prepare( + 'INSERT INTO `astro_assets` (`parent_id`, `lft`, `rgt`, `level`, `name`, `title`, `rules`) ' + . 'VALUES (740, :lft, :rgt, :level, :name, :title, \'{}\')' + ); + $insertAssetStmt->execute([ + ':lft' => $newLeft, + ':rgt' => $newLeft + 1, + ':level' => (int) $categoryAsset['level'] + 1, + ':name' => 'com_content.article.' . $articleId, + ':title' => $title, + ]); + + $pdo->prepare('UPDATE `astro_content` SET `asset_id` = :asset_id WHERE `id` = :id') + ->execute([ + ':asset_id' => (int) $pdo->lastInsertId(), + ':id' => $articleId, + ]); + + $result = [ + 'success' => true, + 'message' => sprintf('%s wurde in Joomla angelegt.', $title), + 'action' => 'created', + ]; + } + + // Joomla 5 zeigt Artikel nur mit einer Workflow-Stufenzuordnung zuverlässig im Backend an. + $workflowStageStmt = $pdo->query( + 'SELECT `id` FROM `astro_workflow_stages` ' + . 'WHERE `workflow_id` = 1 AND `default` = 1 AND `published` = 1 ' + . 'ORDER BY `id` ASC LIMIT 1' + ); + $workflowStageId = (int) $workflowStageStmt->fetchColumn(); + if ($workflowStageId <= 0) { + throw new RuntimeException('Die Joomla-Standard-Workflow-Stufe wurde nicht gefunden.'); + } + + $workflowAssociationStmt = $pdo->prepare( + 'INSERT IGNORE INTO `astro_workflow_associations` (`item_id`, `stage_id`, `extension`) ' + . 'VALUES (:item_id, :stage_id, \'com_content.article\')' + ); + $workflowAssociationStmt->execute([ + ':item_id' => $articleId, + ':stage_id' => $workflowStageId, + ]); + + $pdo->commit(); + } catch (Throwable $e) { + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } + throw $e; + } + + respondWithJoomlaPreviewResult(200, $result); +} catch (Throwable $e) { + error_log('Joomla-Monatsvorschau konnte nicht gespeichert werden: ' . $e->getMessage()); + respondWithJoomlaPreviewResult(500, [ + 'success' => false, + 'message' => 'Die Monatsvorschau konnte nicht in Joomla gespeichert werden.', + ]); +} diff --git a/public/monatsvorhersage.php b/public/monatsvorhersage.php index 57d24cc..a6eaf92 100644 --- a/public/monatsvorhersage.php +++ b/public/monatsvorhersage.php @@ -3,6 +3,12 @@ declare(strict_types=1); session_start(); +require_once __DIR__ . '/auth_helpers.php'; + +if (isLocalDevelopmentRequest()) { + set_time_limit(180); +} + $loggedIn = isset($_SESSION['user_id']); $pageTitle = 'Monatsvorhersage - AstroTools'; $bodyClass = 'admin-page monatsvorhersage-page'; @@ -19,6 +25,10 @@ if (!isset($_SESSION['observation_tip_insert_token']) || !is_string($_SESSION['o $_SESSION['observation_tip_insert_token'] = bin2hex(random_bytes(16)); } $observationTipInsertToken = $_SESSION['observation_tip_insert_token']; +if (!isset($_SESSION['joomla_monthly_preview_token']) || !is_string($_SESSION['joomla_monthly_preview_token'])) { + $_SESSION['joomla_monthly_preview_token'] = bin2hex(random_bytes(16)); +} +$joomlaMonthlyPreviewToken = $_SESSION['joomla_monthly_preview_token']; // ------------------------------------------------------------------------- // Bereits berechnete Bausteine der Monatsvorschau @@ -264,6 +274,7 @@ $observationTipInsertSql = ''; $observationTipCalendarIcs = ''; $observationTipInsertPayloadJson = '[]'; $observationTipInsertEndpoint = $publicBasePath . 'ajax/insert_observationtips.php'; +$joomlaMonthlyPreviewEndpoint = $publicBasePath . 'ajax/save_monthly_preview_to_joomla.php'; $chartConfig = null; $chartConfigJson = 'null'; $starDataJson = '[]'; @@ -2903,6 +2914,11 @@ $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatserei + + +
@@ -3389,6 +3405,8 @@ const MONTH_EVENT_LIST = = $monthEventListJson ?: '[]' ?>; const MONTH_CHART_ERROR = = $starDataErrorJson ?: 'null' ?>; const OBSERVATION_TIP_ICAL = = $observationTipCalendarIcsJson ?: '""' ?>; const OBSERVATION_TIP_ICAL_FILENAME = = $observationTipCalendarFilenameJson ?: '"astrotools-monatsereignisse.ics"' ?>; +const JOOMLA_MONTHLY_PREVIEW_ENDPOINT = = json_encode($joomlaMonthlyPreviewEndpoint, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>; +const JOOMLA_MONTHLY_PREVIEW_TOKEN = = json_encode($joomlaMonthlyPreviewToken, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>; const MONTH_CHART_EXPORT_LOGO_SRC = = json_encode($publicBasePath . 'images/logoAM_ws.png', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>; const MONTH_CHART_COMET_K = 0.01720209895; @@ -6160,6 +6178,7 @@ function monthChartCometRaDec(comet, date) { (function () { const downloadButton = document.getElementById('downloadMonatsueberblickZip'); const copyButton = document.getElementById('copyMonatsueberblickHtml'); + const saveToJoomlaButton = document.getElementById('saveMonatsueberblickToJoomla'); const statusEl = document.getElementById('monatsueberblickZipStatus'); const overviewEl = document.getElementById('monatsueberblick'); const polarCanvas = document.getElementById('monatskarteCanvas'); @@ -6455,6 +6474,41 @@ function monthChartCometRaDec(comet, date) { copyButton.disabled = false; } }); + + if (saveToJoomlaButton) { + saveToJoomlaButton.addEventListener('click', async function () { + const year = Number(MONTH_CHART_CONFIG.year || 0); + const month = Number(MONTH_CHART_CONFIG.monthNumber || MONTH_CHART_CONFIG.month || 0); + if (!Number.isInteger(year) || !Number.isInteger(month)) { + statusEl.textContent = 'Monat für den Joomla-Export konnte nicht bestimmt werden.'; + return; + } + + try { + statusEl.textContent = 'Monatsvorschau wird für Joomla erstellt...'; + saveToJoomlaButton.disabled = true; + const formData = new FormData(); + formData.append('joomla_monthly_preview_token', JOOMLA_MONTHLY_PREVIEW_TOKEN); + formData.append('year', String(year)); + formData.append('month', String(month)); + formData.append('html', await overviewHtml(await createOverviewImages())); + + const response = await fetch(JOOMLA_MONTHLY_PREVIEW_ENDPOINT, { + method: 'POST', + body: formData, + headers: { 'X-Requested-With': 'fetch' }, + }); + const payload = await response.json(); + statusEl.textContent = payload && payload.message + ? payload.message + : 'Die Monatsvorschau konnte nicht in Joomla gespeichert werden.'; + } catch (error) { + statusEl.textContent = 'Die Monatsvorschau konnte nicht in Joomla gespeichert werden.'; + } finally { + saveToJoomlaButton.disabled = false; + } + }); + } })();