diff --git a/acp/recenttopics_module.php b/acp/recenttopics_module.php index 9838519..39d0a80 100644 --- a/acp/recenttopics_module.php +++ b/acp/recenttopics_module.php @@ -76,6 +76,14 @@ public function main($id, $mode) trigger_error($language->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } + // Check the excluded topic IDs before saving anything, so invalid input is reported + // instead of being dropped while the page says the settings were saved (issue #217) + $rt_anti_topics = self::normalise_anti_topics($request->variable('rt_anti_topics', '')); + if ($rt_anti_topics === null) + { + trigger_error($language->lang('RT_ANTI_TOPICS_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); + } + /* * acp options for everyone */ @@ -95,22 +103,8 @@ public function main($id, $mode) // Show announcements first $config->set('rt_announcements_first', $request->variable('rt_announcements_first', 0) ? 1 : 0); - // variable should be '' as it is a string ("1, 2, 3928") here, not an integer. - $rt_anti_topics = $request->variable('rt_anti_topics', ''); - $ants = explode(',', $rt_anti_topics); - $check_ants = true; - foreach ($ants as $ant) - { - if (!is_numeric($ant)) - { - $check_ants = false; - } - } - - if ($check_ants) - { - $config->set('rt_anti_topics', $rt_anti_topics); - } + // Excluded topic IDs, already checked above + $config->set('rt_anti_topics', $rt_anti_topics); $rt_parents = $request->variable('rt_parents', false); $config->set('rt_parents', $rt_parents); @@ -315,6 +309,37 @@ public function main($id, $mode) } + /** + * Turn the ACP "Excluded topic IDs" input into the value to store (issue #217). + * + * Entries are trimmed and empty ones (a trailing comma, a blank field) are dropped; duplicates + * are removed. An empty result becomes '0', which excludes nothing, so the field can be cleared. + * + * @param string $input Raw comma-separated input, e.g. "7, 9" + * @return string|null Normalised list such as "7,9", or null if an entry is not a whole number + */ + public static function normalise_anti_topics($input) + { + $ids = []; + foreach (explode(',', (string) $input) as $entry) + { + $entry = trim($entry); + if ($entry === '') + { + continue; + } + if (!ctype_digit($entry)) + { + return null; + } + $ids[] = (string) (int) $entry; + } + + $ids = array_values(array_unique($ids)); + + return $ids === [] ? '0' : implode(',', $ids); + } + /** * Retrieve latest version using phpBB's file_downloader * diff --git a/language/ar/info_acp_recenttopics.php b/language/ar/info_acp_recenttopics.php index f95e2d9..af7e73a 100644 --- a/language/ar/info_acp_recenttopics.php +++ b/language/ar/info_acp_recenttopics.php @@ -47,6 +47,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'ينقل الإعلانات والإعلانات العامة في كل صفحة من القائمة إلى أعلى تلك الصفحة. لا تتم إضافة الإعلانات الأقدم غير الموجودة في الصفحة.', 'RT_ANTI_TOPICS' => 'المواضيع المُستبعدة ', 'RT_ANTI_TOPICS_EXP' => 'اكتب علامة الفاصلة ", " بين أرقام المواضيع التي تريد عدم ظهورها في "أحدث المواضيع" ( مثال : 7,9 )', + 'RT_ANTI_TOPICS_INVALID' => 'يجب أن تكون معرفات المواضيع المستثناة أرقامًا صحيحة مفصولة بفواصل، مثل 7,9. لم يتم حفظ أي شيء.', 'RT_PARENTS' => 'إظهار المنتدى الأب ', 'RT_PARENTS_EXP' => 'إظهار اسم "المنتدى الأب" في التفاصيل المذكورة تحت اسم الموضوع.', 'RT_TOPIC_LINK_TO' => 'رابط عنوان الموضوع يؤدي إلى', diff --git a/language/cs/info_acp_recenttopics.php b/language/cs/info_acp_recenttopics.php index 0cf8869..c5165b8 100644 --- a/language/cs/info_acp_recenttopics.php +++ b/language/cs/info_acp_recenttopics.php @@ -44,6 +44,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Přesune oznámení a globální oznámení na každé stránce seznamu na začátek dané stránky. Starší oznámení, která na stránce nejsou, se nepřidávají.', 'RT_ANTI_TOPICS' => 'Vyloučená témata', 'RT_ANTI_TOPICS_EXP' => 'Identifikátory témat k vyloučení, oddělené čárkou „,“ (příklad: 7,9)
', + 'RT_ANTI_TOPICS_INVALID' => 'ID vyloučených témat musí být celá čísla oddělená čárkami, například 7,9. Nic nebylo uloženo.', 'RT_PARENTS' => 'Zobrazit nadřazená fóra', 'RT_PARENTS_EXP' => 'Zobrazit nadřazená fóra v řádku podrobností pod názvem nedávného tématu.', 'RT_TOPIC_LINK_TO' => 'Odkaz názvu tématu vede na', diff --git a/language/de/info_acp_recenttopics.php b/language/de/info_acp_recenttopics.php index c8406ac..ebcfeb9 100644 --- a/language/de/info_acp_recenttopics.php +++ b/language/de/info_acp_recenttopics.php @@ -63,6 +63,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Verschiebt die Bekanntmachungen und globalen Bekanntmachungen auf jeder Seite der Liste an den Anfang dieser Seite. Ältere Bekanntmachungen, die nicht auf der Seite stehen, werden nicht hinzugefügt.', 'RT_ANTI_TOPICS' => 'Ausgeschlossene Themen', 'RT_ANTI_TOPICS_EXP' => 'Gib die Themen-IDs ein, kommagetrennt (z. B. 7,9), andernfalls 0, um alle Themen anzuzeigen (wie in der URL viewtopic.php?t=12345).', + 'RT_ANTI_TOPICS_INVALID' => 'Die ausgeschlossenen Themen-IDs müssen ganze Zahlen sein, durch Kommas getrennt, zum Beispiel 7,9. Es wurde nichts gespeichert.', 'RT_PARENTS' => 'Übergeordnete Foren anzeigen', 'RT_PARENTS_EXP' => 'Übergeordnete Foren in der Liste der aktuellen Themen anzeigen.', 'RT_TOPIC_LINK_TO' => 'Thementitel verlinkt auf', diff --git a/language/de_x_sie/info_acp_recenttopics.php b/language/de_x_sie/info_acp_recenttopics.php index f042f9a..130db73 100644 --- a/language/de_x_sie/info_acp_recenttopics.php +++ b/language/de_x_sie/info_acp_recenttopics.php @@ -63,6 +63,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Verschiebt die Bekanntmachungen und globalen Bekanntmachungen auf jeder Seite der Liste an den Anfang dieser Seite. Ältere Bekanntmachungen, die nicht auf der Seite stehen, werden nicht hinzugefügt.', 'RT_ANTI_TOPICS' => 'Ausgeschlossene Themen', 'RT_ANTI_TOPICS_EXP' => 'Geben Sie die Themen-IDs ein, kommagetrennt (z. B. 7,9), andernfalls 0, um alle Themen anzuzeigen (wie in der URL viewtopic.php?t=12345).', + 'RT_ANTI_TOPICS_INVALID' => 'Die ausgeschlossenen Themen-IDs müssen ganze Zahlen sein, durch Kommas getrennt, zum Beispiel 7,9. Es wurde nichts gespeichert.', 'RT_PARENTS' => 'Übergeordnete Foren anzeigen', 'RT_PARENTS_EXP' => 'Übergeordnete Foren in der Liste der aktuellen Themen anzeigen.', 'RT_TOPIC_LINK_TO' => 'Thementitel verlinkt auf', diff --git a/language/en/info_acp_recenttopics.php b/language/en/info_acp_recenttopics.php index 20f5aa4..200f390 100644 --- a/language/en/info_acp_recenttopics.php +++ b/language/en/info_acp_recenttopics.php @@ -47,6 +47,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Moves the announcements and global announcements on each page of the list to the top of that page. Older announcements that are not on the page are not added.', 'RT_ANTI_TOPICS' => 'Excluded topic IDs', 'RT_ANTI_TOPICS_EXP' => 'The IDs of topics to exclude, separated by “,” (Example: 7,9)
The value 0 disables this behaviour.', + 'RT_ANTI_TOPICS_INVALID' => 'Excluded topic IDs must be whole numbers separated by commas, for example 7,9. Nothing was saved.', 'RT_PARENTS' => 'Display parent forums', 'RT_PARENTS_EXP' => 'Display parent forums inside the topic row of recent topics.', 'RT_TOPIC_LINK_TO' => 'Topic title links to', diff --git a/language/es/info_acp_recenttopics.php b/language/es/info_acp_recenttopics.php index 7852ac2..734a841 100644 --- a/language/es/info_acp_recenttopics.php +++ b/language/es/info_acp_recenttopics.php @@ -47,6 +47,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Coloca los anuncios y anuncios globales de cada página de la lista al principio de esa página. Los anuncios más antiguos que no estén en la página no se añaden.', 'RT_ANTI_TOPICS' => 'Temas excluidos', 'RT_ANTI_TOPICS_EXP' => 'Las IDs de los temas a excluir, separados por "," (Por ejemplo: 7,9)
Si no quiere excluir un tema, simplemente introduzca 0.', + 'RT_ANTI_TOPICS_INVALID' => 'Los ID de temas excluidos deben ser números enteros separados por comas, por ejemplo 7,9. No se ha guardado nada.', 'RT_PARENTS' => 'Mostrar foros padre', 'RT_PARENTS_EXP' => 'Mostrar foros padre dentro de la fila del tema de "Temas Recientes".', 'RT_TOPIC_LINK_TO' => 'El título del tema enlaza a', diff --git a/language/es_x_tu/info_acp_recenttopics.php b/language/es_x_tu/info_acp_recenttopics.php index e92a76a..0d0f431 100644 --- a/language/es_x_tu/info_acp_recenttopics.php +++ b/language/es_x_tu/info_acp_recenttopics.php @@ -47,6 +47,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Coloca los anuncios y anuncios globales de cada página de la lista al principio de esa página. Los anuncios más antiguos que no estén en la página no se añaden.', 'RT_ANTI_TOPICS' => 'Temas excluidos', 'RT_ANTI_TOPICS_EXP' => 'Las IDs de los temas a excluír, separados por "," (Por ejemplo: 7,9)
Si no quieres excluir un tema, simplemente introduce 0.', + 'RT_ANTI_TOPICS_INVALID' => 'Los ID de temas excluidos deben ser números enteros separados por comas, por ejemplo 7,9. No se ha guardado nada.', 'RT_PARENTS' => 'Mostrar foros padre', 'RT_PARENTS_EXP' => 'Mostrar foros padre dentro de la fila del tema de "Temas Recientes".', 'RT_TOPIC_LINK_TO' => 'El título del tema enlaza a', diff --git a/language/fr/info_acp_recenttopics.php b/language/fr/info_acp_recenttopics.php index 1a631ad..bb2b1dc 100644 --- a/language/fr/info_acp_recenttopics.php +++ b/language/fr/info_acp_recenttopics.php @@ -63,6 +63,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Place les annonces et annonces globales de chaque page de la liste en haut de cette page. Les annonces plus anciennes qui ne figurent pas sur la page ne sont pas ajoutées.', 'RT_ANTI_TOPICS' => 'Exclure des sujets de l’affichage', 'RT_ANTI_TOPICS_EXP' => 'Permet de saisir les ID, séparés par une virgule (exemple : 7,9), des sujets à exclure de l’affichage des sujets récents.
Pour afficher tous les sujets saisir la valeur 0.', + 'RT_ANTI_TOPICS_INVALID' => 'Les ID des sujets exclus doivent être des nombres entiers séparés par des virgules, par exemple 7,9. Rien n’a été enregistré.', 'RT_PARENTS' => 'Afficher les forums parents', 'RT_PARENTS_EXP' => 'Permet d’afficher les forums parents dans l’arborescence des forums de l’affichage des sujets récents.', 'RT_TOPIC_LINK_TO' => 'Le titre du sujet renvoie vers', diff --git a/language/nl/info_acp_recenttopics.php b/language/nl/info_acp_recenttopics.php index c1f2d17..beacb21 100644 --- a/language/nl/info_acp_recenttopics.php +++ b/language/nl/info_acp_recenttopics.php @@ -63,6 +63,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Plaatst de mededelingen en algemene mededelingen op elke pagina van de lijst bovenaan die pagina. Oudere mededelingen die niet op de pagina staan, worden niet toegevoegd.', 'RT_ANTI_TOPICS' => 'Uitgesloten onderwerpen', 'RT_ANTI_TOPICS_EXP' => 'Vul de onderwerp id’s in (bijvoorbeeld 7,9), anders 0. (deze nummers vind je in de url viewtopic.php?t=12345)', + 'RT_ANTI_TOPICS_INVALID' => 'Uitgesloten onderwerp-ID’s moeten gehele getallen zijn, gescheiden door komma’s, bijvoorbeeld 7,9. Er is niets opgeslagen.', 'RT_PARENTS' => 'Weergeven van hoofdforums', 'RT_PARENTS_EXP' => 'Toon de hoofdforums in de onderwerpregel van de recente onderwerpen.', 'RT_TOPIC_LINK_TO' => 'Onderwerptitel linkt naar', diff --git a/language/pt/info_acp_recenttopics.php b/language/pt/info_acp_recenttopics.php index 674dc3b..bacbfde 100644 --- a/language/pt/info_acp_recenttopics.php +++ b/language/pt/info_acp_recenttopics.php @@ -47,6 +47,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Coloca os anúncios e anúncios globais de cada página da lista no topo dessa página. Anúncios mais antigos que não estejam na página não são adicionados.', 'RT_ANTI_TOPICS' => 'ID de tópico excluído', 'RT_ANTI_TOPICS_EXP' => 'Os IDs de tópicos a excluir, separados por "," (Exemplo: 7,9)
O valor 0 desabilita esse comportamento.', + 'RT_ANTI_TOPICS_INVALID' => 'Os IDs dos tópicos excluídos devem ser números inteiros separados por vírgulas, por exemplo 7,9. Nada foi guardado.', 'RT_PARENTS' => 'Mostrar Fórum Pai', 'RT_PARENTS_EXP' => 'Exibir fóruns pai dentro da linha tópico de tópicos recentes.', 'RT_TOPIC_LINK_TO' => 'Título do tópico liga para', diff --git a/language/ru/info_acp_recenttopics.php b/language/ru/info_acp_recenttopics.php index 0146860..bd5124d 100644 --- a/language/ru/info_acp_recenttopics.php +++ b/language/ru/info_acp_recenttopics.php @@ -46,6 +46,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Перемещает объявления и глобальные объявления на каждой странице списка в начало этой страницы. Более старые объявления, которых нет на странице, не добавляются.', 'RT_ANTI_TOPICS' => 'Исключённые темы', 'RT_ANTI_TOPICS_EXP' => 'Разделённый запятыми список идентификаторов тем, которые не должны отображаться в списке последних тем (например: 7, 9)
Установите 0 для отключения данной функции.', + 'RT_ANTI_TOPICS_INVALID' => 'ID исключённых тем должны быть целыми числами, разделёнными запятыми, например 7,9. Ничего не сохранено.', 'RT_PARENTS' => 'Показать родительские форумы', 'RT_PARENTS_EXP' => 'Показать родительские форумы внутри тем в блоке последних тем.', 'RT_TOPIC_LINK_TO' => 'Ссылка заголовка темы ведёт на', diff --git a/language/sk/info_acp_recenttopics.php b/language/sk/info_acp_recenttopics.php index 183578d..5c886f6 100644 --- a/language/sk/info_acp_recenttopics.php +++ b/language/sk/info_acp_recenttopics.php @@ -47,6 +47,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Presunie oznámenia a globálne oznámenia na každej stránke zoznamu na začiatok danej stránky. Staršie oznámenia, ktoré na stránke nie sú, sa nepridávajú.', 'RT_ANTI_TOPICS' => 'Vylúčené témy', 'RT_ANTI_TOPICS_EXP' => 'ID tém na vylúčenie, oddelené čiarkou „," (príklad: 7,9)
Hodnota 0 túto funkciu vypne.', + 'RT_ANTI_TOPICS_INVALID' => 'ID vylúčených tém musia byť celé čísla oddelené čiarkami, napríklad 7,9. Nič nebolo uložené.', 'RT_PARENTS' => 'Zobraziť nadradené fóra', 'RT_PARENTS_EXP' => 'Zobraziť nadradené fóra v riadku podrobností pod názvom najnovšej témy.', 'RT_TOPIC_LINK_TO' => 'Odkaz názvu témy vedie na', diff --git a/language/sv/info_acp_recenttopics.php b/language/sv/info_acp_recenttopics.php index 130e7f5..d6e0153 100644 --- a/language/sv/info_acp_recenttopics.php +++ b/language/sv/info_acp_recenttopics.php @@ -48,6 +48,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Flyttar meddelanden och globala meddelanden på varje sida i listan överst på den sidan. Äldre meddelanden som inte finns på sidan läggs inte till.', 'RT_ANTI_TOPICS' => 'Exkluderade tråd-ID:n', 'RT_ANTI_TOPICS_EXP' => 'ID:n för trådar att exkludera, separerade med "," (Exempel: 7,9)
Värdet 0 inaktiverar denna funktion.', + 'RT_ANTI_TOPICS_INVALID' => 'Uteslutna tråd-ID:n måste vara heltal åtskilda med kommatecken, till exempel 7,9. Inget sparades.', 'RT_PARENTS' => 'Visa överordnade forum', 'RT_PARENTS_EXP' => 'Visa överordnade forum i trådraden för senaste trådar.', 'RT_TOPIC_LINK_TO' => 'Trådtiteln länkar till', diff --git a/language/uk/info_acp_recenttopics.php b/language/uk/info_acp_recenttopics.php index 22b1be2..bc0edc5 100644 --- a/language/uk/info_acp_recenttopics.php +++ b/language/uk/info_acp_recenttopics.php @@ -47,6 +47,7 @@ 'RT_ANNOUNCEMENTS_FIRST_EXP' => 'Переміщує оголошення та глобальні оголошення на кожній сторінці списку на початок цієї сторінки. Старіші оголошення, яких немає на сторінці, не додаються.', 'RT_ANTI_TOPICS' => 'Не відображати теми з вказаними ID', 'RT_ANTI_TOPICS_EXP' => 'Теми з вказаними ID будуть виключені зі списку відображення. Кожен ID теми має бути розділений комою “,” (Наприклад: 7,9)
Значення "0" деактивує цей список виключення.', + 'RT_ANTI_TOPICS_INVALID' => 'ID виключених тем мають бути цілими числами, розділеними комами, наприклад 7,9. Нічого не збережено.', 'RT_PARENTS' => 'Відображати батьківські форуми', 'RT_PARENTS_EXP' => 'Відображати батьківські форуми в рядку теми.', 'RT_TOPIC_LINK_TO' => 'Посилання заголовка теми веде на', diff --git a/tests/acp/anti_topics_test.php b/tests/acp/anti_topics_test.php new file mode 100644 index 0000000..93bdc6e --- /dev/null +++ b/tests/acp/anti_topics_test.php @@ -0,0 +1,47 @@ + value to store, or null when the input must be rejected. + */ + public function anti_topics_data() + { + return [ + 'single id' => ['7', '7'], + 'list' => ['7,9', '7,9'], + 'spaces are trimmed' => [' 7 , 9 ', '7,9'], + 'duplicates are dropped' => ['7,9,7', '7,9'], + 'trailing comma is ignored' => ['7,9,', '7,9'], + 'empty field excludes nothing' => ['', '0'], + 'blank field excludes nothing' => [' ', '0'], + 'zero keeps meaning nothing' => ['0', '0'], + 'text is rejected' => ['7,abc', null], + 'decimal is rejected' => ['7.5', null], + 'negative is rejected' => ['-3', null], + ]; + } + + /** + * Clearing the field must be possible and invalid entries must be reported, not silently + * dropped while the ACP says the settings were saved (#217). + * + * @dataProvider anti_topics_data + */ + public function test_normalise_anti_topics($input, $expected) + { + $this->assertSame($expected, \avathar\recenttopics\acp\recenttopics_module::normalise_anti_topics($input)); + } +} diff --git a/tests/functional/recenttopics_test.php b/tests/functional/recenttopics_test.php index 4755f94..f990473 100644 --- a/tests/functional/recenttopics_test.php +++ b/tests/functional/recenttopics_test.php @@ -263,6 +263,42 @@ public function test_acp_saves_announcements_first() $this->set_config('rt_announcements_first', 0); } + /** + * Invalid "Excluded topic IDs" input is reported and nothing is saved (#217). + */ + public function test_acp_rejects_invalid_anti_topics() + { + $this->login(); + $this->admin_login(); + $this->add_lang_ext('avathar/recenttopics', 'info_acp_recenttopics'); + $this->set_config('rt_anti_topics', '7'); + + $crawler = self::request('GET', $this->acp_module_url()); + $form = $crawler->selectButton('submit')->form(); + $form['rt_anti_topics'] = '7,abc'; + $crawler = self::submit($form); + + $this->assertStringContainsString($this->lang('RT_ANTI_TOPICS_INVALID'), $crawler->text()); + $this->assertSame('7', $this->get_config_value('rt_anti_topics'), 'An invalid list must not be saved'); + } + + /** + * Clearing "Excluded topic IDs" is saved as 0, excluding nothing (#217). + */ + public function test_acp_clears_anti_topics() + { + $this->login(); + $this->admin_login(); + $this->set_config('rt_anti_topics', '7'); + + $crawler = self::request('GET', $this->acp_module_url()); + $form = $crawler->selectButton('submit')->form(); + $form['rt_anti_topics'] = ''; + self::submit($form); + + $this->assertSame('0', $this->get_config_value('rt_anti_topics'), 'An empty field must clear the list'); + } + // ----------------------------------------------------------------------- // Helper // ----------------------------------------------------------------------- @@ -275,6 +311,20 @@ private function acp_module_url() return 'adm/index.php?i=-avathar-recenttopics-acp-recenttopics_module&mode=recenttopics_config&sid=' . $this->sid; } + /** + * Read a phpBB config value straight from the database. + */ + private function get_config_value($name) + { + $db = $this->get_db(); + + $result = $db->sql_query("SELECT config_value FROM phpbb_config WHERE config_name = '" . $db->sql_escape($name) . "'"); + $value = $db->sql_fetchfield('config_value'); + $db->sql_freeresult($result); + + return (string) $value; + } + /** * Read the admin account's stored topics-per-page preference. */ diff --git a/tests/tests.md b/tests/tests.md index d093cff..7d46df4 100644 --- a/tests/tests.md +++ b/tests/tests.md @@ -37,7 +37,8 @@ phpBB fires named events as it runs (see `contrib/events.md`). An event dispatch ``` tests/ ├── acp/ -│ └── acp_template_test.php ACP template assets (no external images) +│ ├── acp_template_test.php ACP template assets (no external images) +│ └── anti_topics_test.php ACP "Excluded topic IDs" input ├── event/ │ ├── listener_test.php Main event listener (board index, ACP, WOL, permissions) │ └── ucp_listener_test.php UCP preferences listener @@ -243,6 +244,12 @@ It reads the template and the image straight from disk. | `test_acp_template_loads_no_external_patreon_image` | `adm/style/acp_recenttopics.html` | No `patreon.com/external` URL; the image uses `{{ U_PATREON_BUTTON }}`. Before the fix every ACP page view made the admin's browser call Patreon's CDN (#200) | | `test_patreon_button_is_bundled` | `adm/style/images/become_a_patron_button.png` | The file exists and is a PNG | +**`acp/anti_topics_test.php`** checks `recenttopics_module::normalise_anti_topics()`, which turns the "Excluded topic IDs" input into the value stored in `rt_anti_topics`. Its data provider has one row per input. + +| Test | Scenario | What it verifies | +|------|----------|-----------------| +| `test_normalise_anti_topics` | `7`, `7,9`, ` 7 , 9 `, `7,9,7`, `7,9,`, empty, blank, `0`, `7,abc`, `7.5`, `-3` | Valid lists are trimmed and de-duplicated; an empty field becomes `0` (exclude nothing), so it can be cleared; text, decimals and negatives return `null`, which makes the ACP show an error and save nothing (#217) | + --- ## 10. Announcements first (`core/announcements_first_test.php`) @@ -293,6 +300,8 @@ Logs in as the admin account, optionally creates topics or changes config values | `test_acp_patreon_button_is_local` | Logged in as admin; GET the Recent Topics ACP page | The Patreon button's `src` is the bundled image, not `patreon.com`, and that image URL returns 200 (#200) | | `test_announcements_first_on_index` | `rt_announcements_first` = 1; create an announcement, then a newer normal topic; GET `index.php` | The announcement is listed above the newer normal topic in `#recent-topics-box` (#201) | | `test_acp_saves_announcements_first` | Admin ticks "Show announcements first" in the ACP and submits | `rt_announcements_first` is stored as 1 (#201) | +| `test_acp_rejects_invalid_anti_topics` | Admin submits `7,abc` as excluded topic IDs | The `RT_ANTI_TOPICS_INVALID` error is shown and the stored value stays `7` (#217) | +| `test_acp_clears_anti_topics` | Admin empties the field and submits | `rt_anti_topics` is stored as `0` (#217) | | `test_ucp_preferences` | Logged in as admin; GET `/ucp.php?i=ucp_prefs&mode=view` | `input[name="rt_enable"]` and `input[name="rt_number"]` are present on the preferences page | ---