diff --git a/acp/recenttopics_module.php b/acp/recenttopics_module.php
index 8b0efa0..11d9655 100644
--- a/acp/recenttopics_module.php
+++ b/acp/recenttopics_module.php
@@ -45,7 +45,7 @@ class recenttopics_module
*/
public function main($id, $mode)
{
- global $phpbb_container;
+ global $phpbb_container, $phpbb_root_path;
$config = $phpbb_container->get('config');
$request = $phpbb_container->get('request');
@@ -298,7 +298,9 @@ public function main($id, $mode)
'RT_SHOW_LIKES' => (int) $config['rt_show_likes'],
'RT_SIDE_SHOW_DATE' => (int) $config['rt_side_show_date'],
'S_POSTLOVE' => $phpbb_container->has('avathar.postlove.topic_likes'),
- 'S_RT_OK' => version_compare($ext_version, $latest_version, '=='),
+ // Bundled copy, so the ACP page makes no request to Patreon's CDN (issue #200)
+ 'U_PATREON_BUTTON' => $phpbb_root_path . 'ext/avathar/recenttopics/adm/style/images/become_a_patron_button.png',
+ 'S_RT_OK' => version_compare($ext_version, $latest_version, '=='),
'S_RT_OLD' => version_compare($ext_version, $latest_version, '<'),
'S_RT_DEV' => version_compare($ext_version, $latest_version, '>'),
'EXT_VERSION' => $ext_version,
diff --git a/adm/style/acp_recenttopics.html b/adm/style/acp_recenttopics.html
index ba24c1b..99d54d3 100644
--- a/adm/style/acp_recenttopics.html
+++ b/adm/style/acp_recenttopics.html
@@ -171,7 +171,7 @@
{{ lang('RECENT_TOPICS') }}
{{ lang('RT_DONATE_EXPLAIN') }}
-
+
diff --git a/adm/style/images/become_a_patron_button.png b/adm/style/images/become_a_patron_button.png
new file mode 100644
index 0000000..291b7bc
Binary files /dev/null and b/adm/style/images/become_a_patron_button.png differ
diff --git a/tests/acp/acp_template_test.php b/tests/acp/acp_template_test.php
new file mode 100644
index 0000000..de04d01
--- /dev/null
+++ b/tests/acp/acp_template_test.php
@@ -0,0 +1,47 @@
+ext_root = dirname(__DIR__, 2);
+ }
+
+ /**
+ * Opening the ACP page must not make the admin's browser call Patreon's CDN (#200).
+ */
+ public function test_acp_template_loads_no_external_patreon_image()
+ {
+ $template = file_get_contents($this->ext_root . '/adm/style/acp_recenttopics.html');
+
+ $this->assertStringNotContainsString('patreon.com/external', $template);
+ $this->assertStringContainsString('{{ U_PATREON_BUTTON }}', $template);
+ }
+
+ /**
+ * The bundled button the template points to ships with the extension.
+ */
+ public function test_patreon_button_is_bundled()
+ {
+ $file = $this->ext_root . '/adm/style/images/become_a_patron_button.png';
+
+ $this->assertFileExists($file);
+ $this->assertSame('image/png', mime_content_type($file));
+ }
+}
diff --git a/tests/functional/recenttopics_test.php b/tests/functional/recenttopics_test.php
index 0e30330..701124c 100644
--- a/tests/functional/recenttopics_test.php
+++ b/tests/functional/recenttopics_test.php
@@ -192,6 +192,25 @@ public function test_acp_reset_works_with_valid_form_token()
'Submitting the genuine ACP form must still reset user preferences');
}
+ /**
+ * The ACP donate button uses the extension's bundled image, not Patreon's CDN,
+ * and that image is actually served (#200).
+ */
+ public function test_acp_patreon_button_is_local()
+ {
+ $this->login();
+ $this->admin_login();
+
+ $crawler = self::request('GET', $this->acp_module_url());
+ $src = $crawler->filter('a[href*="patreon.com"] img')->attr('src');
+
+ $this->assertStringNotContainsString('patreon.com', $src);
+ $this->assertStringEndsWith('ext/avathar/recenttopics/adm/style/images/become_a_patron_button.png', $src);
+
+ self::request('GET', 'ext/avathar/recenttopics/adm/style/images/become_a_patron_button.png', array(), false);
+ $this->assertSame(200, self::$client->getResponse()->getStatus());
+ }
+
// -----------------------------------------------------------------------
// Helper
// -----------------------------------------------------------------------
diff --git a/tests/tests.md b/tests/tests.md
index f5b9346..3c71767 100644
--- a/tests/tests.md
+++ b/tests/tests.md
@@ -36,6 +36,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)
├── event/
│ ├── listener_test.php Main event listener (board index, ACP, WOL, permissions)
│ └── ucp_listener_test.php UCP preferences listener
@@ -224,7 +226,22 @@ It loads `recenttopics.php` from every language pack and checks that `VIEWING_RE
---
-## 9. Functional tests (`functional/recenttopics_test.php`)
+## 9. ACP template assets (`acp/acp_template_test.php`)
+
+**What this code does:**
+The ACP page shows a "Become a patron" button. The image ships with the extension (`adm/style/images/become_a_patron_button.png`), and the ACP module passes its URL to the template as `U_PATREON_BUTTON`.
+
+**What this test file does:**
+It reads the template and the image straight from disk.
+
+| Test | Scenario | What it verifies |
+|------|----------|-----------------|
+| `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 |
+
+---
+
+## 10. Functional tests (`functional/recenttopics_test.php`)
**What this code does:**
These tests start a real phpBB installation (using the test framework's built-in install), enable the extension, and make HTTP requests using a real browser-like crawler (Symfony DomCrawler). They check the actual rendered HTML for specific elements.
@@ -240,6 +257,7 @@ Logs in as the admin account, optionally creates topics or changes config values
| `test_rt_simple_page` | `rt_page_enable` = 1; GET `/app.php/rt/simple` | `` is present |
| `test_rt_page_disabled` | `rt_page_enable` = 0; GET `/app.php/rt` | Page still loads (no 500 error); `` is present; `#recent-topics-box` must NOT appear |
| `test_index_has_recent_topics` | `rt_index` = 1; create a topic; GET `index.php` | `` is present; `#recent-topics-box` contains the created topic title |
+| `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_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 |
---