WebOrbiton
v2.0.0.1

Publisium

144 lines · 5.8 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. require_once __DIR__ . '/../includes/slugger.php';
  6. ​
  7. $title = trim((string) ($_POST['title'] ?? ''));
  8. $categoryId = (int) ($_POST['category_id'] ?? 0) ?: null;
  9. $excerpt = trim((string) ($_POST['excerpt'] ?? ''));
  10. $accessType = ($_POST['access_type'] ?? 'free') === 'paid' ? 'paid' : 'free';
  11. $seoTitle = trim((string) ($_POST['seo_title'] ?? ''));
  12. $seoDescription = trim((string) ($_POST['seo_description'] ?? ''));
  13. $noindex = isset($_POST['noindex']) ? 1 : 0;
  14. $coverImage = trim((string) ($_POST['cover_image_path'] ?? ''));
  15. $blocksJson = BlockEditor::sanitize((string) ($_POST['blocks_json'] ?? '{"blocks":[]}'));
  16. $articleId = (int) ($_POST['article_id'] ?? 0) ?: null;
  17. $requestedAction = (string) ($_POST['publish_action'] ?? '');
  18. if (!in_array($requestedAction, ['save_draft', 'update', 'submit_for_review', 'publish_now', 'schedule'], true)) {
  19. // No button pressed (Ctrl+S or Enter): an existing article keeps its status, a new one starts as a draft.
  20. $requestedAction = $articleId !== null ? 'update' : 'save_draft';
  21. }
  22. $scheduledInput = trim((string) ($_POST['scheduled_at'] ?? ''));
  23. $scheduledTimestamp = $scheduledInput !== '' ? strtotime($scheduledInput) : false;
  24. $scheduledAt = $scheduledTimestamp !== false ? date('Y-m-d H:i:s', $scheduledTimestamp) : '';
  25. ​
  26. if ($title === '') {
  27. return ['Give your article a title first.', 'error', null];
  28. }
  29. ​
  30. $slug = Slugger::make(trim((string) ($_POST['slug'] ?? '')));
  31. if ($slug === '') {
  32. $slug = Slugger::make($title);
  33. }
  34. $slug = $slug . '-' . substr(bin2hex(random_bytes(3)), 0, 6);
  35. ​
  36. $db = Database::site();
  37. ​
  38. if ($articleId !== null) {
  39. $existingStatement = $db->prepare('SELECT * FROM articles WHERE id = :id LIMIT 1');
  40. $existingStatement->execute(['id' => $articleId]);
  41. $existing = $existingStatement->fetch();
  42. ​
  43. if (!$existing) {
  44. return ['We couldn’t find this article. It may have been deleted.', 'error', null];
  45. }
  46. ​
  47. if ($existing['deleted_at'] !== null) {
  48. return ['This article is in the trash. Restore it first, then save your changes.', 'error', null];
  49. }
  50. ​
  51. $isOwner = (int) $existing['author_id'] === (int) $currentUser['id'];
  52. $canEditAny = Auth::hasRoleAtLeast(Auth::ROLE_EDITOR_IN_CHIEF);
  53. $canProofread = Auth::role() === Auth::ROLE_PROOFREADER;
  54. ​
  55. if (!$isOwner && !$canEditAny && !$canProofread) {
  56. return ['Only the author and editors can edit this article.', 'error', null];
  57. }
  58. ​
  59. if ($canProofread && !$isOwner) {
  60. $update = $db->prepare(
  61. 'UPDATE articles SET content_blocks = :content, updated_at = NOW() WHERE id = :id'
  62. );
  63. $update->execute(['content' => $blocksJson, 'id' => $articleId]);
  64. return ['Your corrections are saved.', 'success', $articleId];
  65. }
  66. ​
  67. $canPublish = Auth::canPublishDirectly() || $canEditAny;
  68. $status = $existing['status'];
  69. ​
  70. if ($requestedAction === 'submit_for_review') {
  71. $status = 'pending_review';
  72. } elseif ($requestedAction === 'publish_now') {
  73. $status = $canPublish ? 'published' : 'pending_review';
  74. } elseif ($requestedAction === 'schedule' && $scheduledAt !== '') {
  75. $status = $canPublish ? 'scheduled' : 'pending_review';
  76. } elseif ($requestedAction === 'save_draft' || !$canPublish) {
  77. $status = 'draft';
  78. }
  79. ​
  80. $publishedAt = $status === 'published' ? ($existing['published_at'] ?? date('Y-m-d H:i:s')) : $existing['published_at'];
  81. ​
  82. $update = $db->prepare(
  83. 'UPDATE articles SET category_id = :category_id, title = :title, excerpt = :excerpt,
  84. content_blocks = :content, cover_image_path = :cover, access_type = :access_type,
  85. status = :status, seo_title = :seo_title, seo_description = :seo_description, noindex = :noindex,
  86. scheduled_at = :scheduled_at, published_at = :published_at, updated_at = NOW()
  87. WHERE id = :id'
  88. );
  89. $update->execute([
  90. 'category_id' => $categoryId,
  91. 'title' => $title,
  92. 'excerpt' => $excerpt,
  93. 'content' => $blocksJson,
  94. 'cover' => $coverImage,
  95. 'access_type' => $accessType,
  96. 'status' => $status,
  97. 'seo_title' => $seoTitle,
  98. 'seo_description' => $seoDescription,
  99. 'noindex' => $noindex,
  100. 'scheduled_at' => $status === 'scheduled' ? ($scheduledAt !== '' ? $scheduledAt : $existing['scheduled_at']) : null,
  101. 'published_at' => $publishedAt,
  102. 'id' => $articleId,
  103. ]);
  104. ​
  105. return ['Changes saved.', 'success', $articleId];
  106. }
  107. ​
  108. $status = 'draft';
  109. if ($requestedAction === 'submit_for_review') {
  110. $status = 'pending_review';
  111. } elseif ($requestedAction === 'publish_now') {
  112. $status = Auth::canPublishDirectly() ? 'published' : 'pending_review';
  113. } elseif ($requestedAction === 'schedule' && $scheduledAt !== '') {
  114. $status = Auth::canPublishDirectly() ? 'scheduled' : 'pending_review';
  115. }
  116. ​
  117. $insert = $db->prepare(
  118. 'INSERT INTO articles (author_id, category_id, title, slug, excerpt, content_blocks, cover_image_path,
  119. access_type, status, seo_title, seo_description, noindex, scheduled_at, published_at)
  120. VALUES (:author_id, :category_id, :title, :slug, :excerpt, :content, :cover,
  121. :access_type, :status, :seo_title, :seo_description, :noindex, :scheduled_at, :published_at)'
  122. );
  123. $insert->execute([
  124. 'author_id' => $currentUser['id'],
  125. 'category_id' => $categoryId,
  126. 'title' => $title,
  127. 'slug' => $slug,
  128. 'excerpt' => $excerpt,
  129. 'content' => $blocksJson,
  130. 'cover' => $coverImage,
  131. 'access_type' => $accessType,
  132. 'status' => $status,
  133. 'seo_title' => $seoTitle,
  134. 'seo_description' => $seoDescription,
  135. 'noindex' => $noindex,
  136. 'scheduled_at' => $status === 'scheduled' ? $scheduledAt : null,
  137. 'published_at' => $status === 'published' ? date('Y-m-d H:i:s') : null,
  138. ]);
  139. ​
  140. $newId = (int) $db->lastInsertId();
  141. CleanUrls::sync();
  142. ​
  143. return ['Article saved.', 'success', $newId];
  144. ​