1<?php
2/**
3 * Title: News
4 * Icon: fa-newspaper-o
5 * Group: Content
6 * Order: 10
7 * Description: Write, edit and remove front-page articles.
8 */
9
10if (!defined('ACP_ROOT')) {
11 http_response_code(403);
12 die('Direct access denied.');
13}
14
15function acp_news_rebuild_cache(): void {
16 $cache = new Cache('engine/cache/news');
17 $cache->setContent(fetchAllNews() ?: []);
18 $cache->save();
19}
20
21// ---------------------------------------------------------------------------
22// Mutations
23// ---------------------------------------------------------------------------
24if ($_SERVER['REQUEST_METHOD'] === 'POST') {
25
26 $do = (string)($_POST['do'] ?? '');
27 $id = intv($_POST['id'] ?? 0);
28
29 if ($do === 'delete' && $id > 0) {
30 db()->execute("DELETE FROM `znote_news` WHERE `id` = ? LIMIT 1;", [$id]);
31 acp_news_rebuild_cache();
32 acp_log('news.delete', '#' . $id);
33 acp_flash_success(t('acp.news.deleted'));
34 acp_redirect('news');
35 }
36
37 if ($do === 'create') {
38 $charId = intv($_POST['selected_char'] ?? 0);
39 $title = trim((string)($_POST['title'] ?? ''));
40 $text = (string)($_POST['text'] ?? '');
41
42 if ($charId > 0 && $title !== '' && trim($text) !== '') {
43 db()->execute("
44 INSERT INTO `znote_news` (`title`, `text`, `date`, `pid`)
45 VALUES (?, ?, ?, ?);
46 ", [$title, $text, time(), $charId]);
47 acp_news_rebuild_cache();
48 acp_log('news.create', $title);
49 acp_flash_success(t('acp.news.published'));
50 acp_redirect('news');
51 }
52
53 acp_flash_error(t('acp.news.fill_fields'));
54 acp_redirect('news', ['action' => 'add']);
55 }
56
57 if ($do === 'update' && $id > 0) {
58 $title = trim((string)($_POST['title'] ?? ''));
59 $text = (string)($_POST['text'] ?? '');
60
61 db()->execute("
62 UPDATE `znote_news`
63 SET `title` = ?, `text` = ?
64 WHERE `id` = ?
65 LIMIT 1;
66 ", [$title, $text, $id]);
67 acp_news_rebuild_cache();
68 acp_log('news.update', $title !== '' ? $title : ('#' . $id));
69 acp_flash_success(t('acp.news.updated'));
70 acp_redirect('news');
71 }
72}
73
74// ---------------------------------------------------------------------------
75// View state
76// ---------------------------------------------------------------------------
77$action = (string)($_GET['action'] ?? '');
78$editId = intv($_GET['id'] ?? 0);
79
80$news = fetchAllNews();
81$news = is_array($news) ? $news : [];
82
83$editing = null;
84if ($action === 'edit' && $editId > 0) {
85 foreach ($news as $n) {
86 if ((int)$n['id'] === $editId) {
87 $editing = $n;
88 break;
89 }
90 }
91 if ($editing === null) {
92 acp_flash_error(t('acp.news.not_found'));
93 acp_redirect('news');
94 }
95}
96
97// Staff characters on the admin's own account can be set as author.
98$authors = [];
99if ($action === 'add') {
100 $charList = user_character_list($user_data['id']);
101 if (is_array($charList)) {
102 foreach ($charList as $row) {
103 $charName = (string)($row['name'] ?? '');
104 if ($charName === '') {
105 continue;
106 }
107 $charId = (int)user_character_id($charName);
108 $charD = user_character_data($charId, 'group_id', 'id');
109 if (is_array($charD) && (int)($charD['group_id'] ?? 0) > 1) {
110 $authors[$charId] = $charName;
111 }
112 }
113 }
114}
115
116?>
117
118<div class="acp-toolbar">
119 <div>
120 <?php if ($action === 'add'): ?>
121 <strong><?= t('acp.news.new_article') ?></strong>
122 <?php elseif ($editing !== null): ?>
123 <strong><?= t('acp.news.editing') ?></strong> <?= h((string)$editing['title']) ?>
124 <?php else: ?>
125 <span class="acp-pill acp-pill--grey"><?= t('acp.news.published_count', ['count' => count($news)]) ?></span>
126 <?php endif; ?>
127 </div>
128 <div class="acp-actions is-tight">
129 <?php if ($action !== ''): ?>
130 <a class="acp-btn acp-btn--ghost acp-btn--sm" href="<?= h(acp_url('news')) ?>"><i class="fa fa-arrow-left"></i> <?= t('acp.news.back_to_list') ?></a>
131 <?php else: ?>
132 <a class="acp-btn" href="<?= h(acp_url('news', ['action' => 'add'])) ?>"><i class="fa fa-plus"></i> <?= t('acp.news.create_article') ?></a>
133 <?php endif; ?>
134 </div>
135</div>
136
137<?php if ($action === 'add' || $editing !== null): ?>
138 <section class="acp-card">
139 <header class="acp-card-head">
140 <h2><?= $editing !== null ? t('acp.news.edit_article') : t('acp.news.new_article') ?></h2>
141 </header>
142 <div class="acp-card-body">
143
144 <?php if ($action === 'add' && !$authors): ?>
145 <div class="acp-flash acp-flash--error">
146 <i class="fa fa-exclamation-triangle"></i>
147 <span>
148 <?= t('acp.news.no_author', ['code' => '<code>group_id > 1</code>']) ?>
149 </span>
150 </div>
151 <?php endif; ?>
152
153 <form method="post">
154 <?= acp_csrf_field() ?>
155 <input type="hidden" name="do" value="<?= $editing !== null ? 'update' : 'create' ?>">
156 <?php if ($editing !== null): ?>
157 <input type="hidden" name="id" value="<?= (int)$editing['id'] ?>">
158 <?php endif; ?>
159
160 <?php if ($action === 'add'): ?>
161 <div class="acp-field">
162 <label class="acp-label" for="selected_char"><?= t('acp.news.publish_as') ?></label>
163 <select class="acp-select" id="selected_char" name="selected_char" <?= $authors ? '' : 'disabled' ?>>
164 <?php foreach ($authors as $charId => $charName): ?>
165 <option value="<?= (int)$charId ?>"><?= h($charName) ?></option>
166 <?php endforeach; ?>
167 </select>
168 </div>
169 <?php endif; ?>
170
171 <div class="acp-field">
172 <label class="acp-label" for="title"><?= t('acp.news.title') ?></label>
173 <input class="acp-input" id="title" name="title" value="<?= $editing !== null ? h((string)$editing['title']) : '' ?>" required>
174 </div>
175
176 <div class="acp-field">
177 <label class="acp-label" for="text"><?= t('acp.news.body') ?></label>
178 <?php acp_editor('text', $editing !== null ? (string)$editing['text'] : '', ['height' => 340]); ?>
179 </div>
180
181 <div class="acp-actions">
182 <button class="acp-btn acp-btn--green" type="submit" <?= ($action === 'add' && !$authors) ? 'disabled' : '' ?>>
183 <i class="fa fa-check"></i> <?= $editing !== null ? t('acp.news.save_changes') : t('acp.news.publish_article') ?>
184 </button>
185 <a class="acp-btn acp-btn--ghost" href="<?= h(acp_url('news')) ?>"><?= t('acp.news.cancel') ?></a>
186 </div>
187 </form>
188 </div>
189 </section>
190
191<?php else: ?>
192
193 <section class="acp-card">
194 <header class="acp-card-head"><h2><?= t('acp.news.published_articles') ?></h2></header>
195 <div class="acp-card-body is-flush">
196 <?php if ($news): ?>
197 <div class="acp-table-wrap">
198 <table class="acp-table" data-sortable>
199 <thead>
200 <tr>
201 <th><?= t('acp.news.col_date') ?></th>
202 <th><?= t('acp.news.col_author') ?></th>
203 <th><?= t('acp.news.col_title') ?></th>
204 <th class="is-num"><?= t('acp.news.col_actions') ?></th>
205 </tr>
206 </thead>
207 <tbody>
208 <?php foreach ($news as $n):
209 $id = (int)($n['id'] ?? 0);
210 $nm = (string)($n['name'] ?? '');
211 ?>
212 <tr>
213 <td class="is-nowrap is-muted"><?= h(getClock((int)($n['date'] ?? 0), true)) ?></td>
214 <td>
215 <a href="<?= h(acp_site('characterprofile.php?name=' . urlencode($nm))) ?>" target="_blank" rel="noopener"><?= h($nm) ?></a>
216 </td>
217 <td><?= h((string)($n['title'] ?? '')) ?></td>
218 <td class="is-num is-nowrap">
219 <a class="acp-btn acp-btn--ghost acp-btn--sm" href="<?= h(acp_url('news', ['action' => 'edit', 'id' => $id])) ?>">
220 <i class="fa fa-pencil"></i> <?= t('acp.news.edit') ?>
221 </a>
222 <form class="acp-inline-form" method="post" data-confirm="<?= h(t('acp.news.confirm_delete')) ?>">
223 <?= acp_csrf_field() ?>
224 <input type="hidden" name="do" value="delete">
225 <input type="hidden" name="id" value="<?= $id ?>">
226 <button class="acp-btn acp-btn--red acp-btn--sm" type="submit"><i class="fa fa-trash"></i> <?= t('acp.news.delete') ?></button>
227 </form>
228 </td>
229 </tr>
230 <?php endforeach; ?>
231 </tbody>
232 </table>
233 </div>
234 <?php else: ?>
235 <?php acp_empty(t('acp.news.empty'), 'fa-newspaper-o'); ?>
236 <?php endif; ?>
237 </div>
238 </section>
239
240<?php endif; ?>
241