Skip to content

Commit 1a4b934

Browse files
committed
feat(plugins): add support for JavaScript in plugins
1 parent 3563948 commit 1a4b934

File tree

15 files changed

+385
-6
lines changed

15 files changed

+385
-6
lines changed

docs/plugins.md

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,16 +231,80 @@ plugin.{PluginName}.{messageKey}
231231
└── language_es.php
232232
```
233233

234-
## 9.8 Complete plugin example with CSS and translations
234+
## 9.8 Plugin JavaScript
235235

236-
See the `EnhancedExample` plugin for a complete working example demonstrating both features:
236+
Plugins can provide pre-compiled JavaScript files that will be automatically injected into both frontend and admin pages.
237+
238+
### 9.8.1 Adding JavaScript to your plugin
239+
240+
Implement the `getScripts()` method in your plugin class:
241+
242+
```php
243+
public function getScripts(): array
244+
{
245+
return [
246+
'assets/script.js', // Frontend script
247+
'assets/admin-script.js' // Admin-specific script
248+
];
249+
}
250+
```
251+
252+
**Important notes:**
253+
- Paths are relative to your plugin directory
254+
- Provide **pre-compiled JavaScript files** only (not TypeScript source)
255+
- JavaScript files are loaded before core scripts
256+
- Scripts are automatically injected at the end of `<body>`
257+
- Works in both frontend and admin areas
258+
259+
### 9.8.2 Plugin directory structure for JavaScript
260+
261+
```
262+
/content/plugins/YourPlugin/
263+
├── YourPluginPlugin.php
264+
└── assets/
265+
├── script.js
266+
└── admin-script.js
267+
```
268+
269+
### 9.8.3 JavaScript best practices
270+
271+
**Example frontend script:**
272+
```javascript
273+
(function() {
274+
'use strict';
275+
276+
// Wait for DOM to be ready
277+
if (document.readyState === 'loading') {
278+
document.addEventListener('DOMContentLoaded', init);
279+
} else {
280+
init();
281+
}
282+
283+
function init() {
284+
console.log('My Plugin: Loaded');
285+
// Your plugin functionality here
286+
}
287+
})();
288+
```
289+
290+
**Security notes:**
291+
- Always use strict mode (`'use strict'`)
292+
- Wrap code in IIFE to avoid global namespace pollution
293+
- Validate and sanitize user input
294+
- Use DOM API safely
295+
296+
## 9.9 Complete plugin example with CSS, JavaScript, and translations
297+
298+
See the `EnhancedExample` plugin for a complete working example demonstrating all features:
237299

238300
```
239301
/content/plugins/EnhancedExample/
240302
├── EnhancedExamplePlugin.php
241303
├── assets/
242304
│ ├── style.css
243-
│ └── admin-style.css
305+
│ ├── admin-style.css
306+
│ ├── script.js
307+
│ └── admin-script.js
244308
└── translations/
245309
├── language_en.php
246310
├── language_de.php
@@ -256,7 +320,7 @@ See the `EnhancedExample` plugin for a complete working example demonstrating bo
256320
<p>{{ 'plugin.EnhancedExample.adminMessage' | translate }}</p>
257321
```
258322

259-
## 9.9 Plugin version history
323+
## 9.10 Plugin version history
260324

261325
- 0.1.0: Initial version, shipped with phpMyFAQ 4.0.0
262-
- 0.2.0: Added support for plugin configuration options, plugin stylesheets and translations, shipped with phpMyFAQ 4.1.0
326+
- 0.2.0: Added support for plugin configuration, stylesheets, JavaScript, and translations, shipped with phpMyFAQ 4.1.0

phpmyfaq/assets/templates/admin/index.twig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,11 @@
314314
style="display: none;" name="keep-phpmyfaq-session-alive"></iframe>
315315
{% endif %}
316316

317+
{% if pluginScripts is defined and pluginScripts is not empty %}
318+
{% for script in pluginScripts %}
319+
<script src="../{{ script }}"></script>
320+
{% endfor %}
321+
{% endif %}
317322
<script type="module" src="../assets/public/backend.js?{{ currentTimeStamp}}"></script>
318323
</body>
319324
</html>

phpmyfaq/assets/templates/default/index.twig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@
225225
{% if isCookieConsentEnabled %}
226226
<script type="module" src="./assets/public/cookieConsent.js"></script>
227227
{% endif %}
228+
{% if pluginScripts is defined and pluginScripts is not empty %}
229+
{% for script in pluginScripts %}
230+
<script src="{{ baseHref }}{{ script }}"></script>
231+
{% endfor %}
232+
{% endif %}
228233
<script type="module" src="./assets/public/frontend.js"></script>
229234
</body>
230235
</html>

phpmyfaq/content/plugins/EnhancedExample/EnhancedExamplePlugin.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ public function getTranslationsPath(): ?string
7676
return 'translations';
7777
}
7878

79+
public function getScripts(): array
80+
{
81+
return [
82+
'assets/script.js', // Frontend script
83+
'assets/admin-script.js' // Admin script
84+
];
85+
}
86+
7987
public function registerEvents(EventDispatcherInterface $eventDispatcher): void
8088
{
8189
$eventDispatcher->addListener('enhanced.greeting', [$this, 'onGreeting']);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Enhanced Example Plugin - Admin JavaScript
3+
*
4+
* This script demonstrates admin-specific JavaScript functionality for plugins.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public License,
7+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
8+
* obtain one at https://mozilla.org/MPL/2.0/.
9+
*
10+
* @package phpMyFAQ
11+
* @author Thorsten Rinne <thorsten@phpmyfaq.de>
12+
* @copyright 2025 phpMyFAQ Team
13+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14+
* @link https://www.phpmyfaq.de
15+
* @since 2025-12-28
16+
*/
17+
18+
/* global document, console */
19+
20+
(function () {
21+
'use strict';
22+
23+
// Wait for DOM to be ready
24+
if (document.readyState === 'loading') {
25+
document.addEventListener('DOMContentLoaded', init);
26+
} else {
27+
init();
28+
}
29+
30+
function init() {
31+
console.log('Enhanced Example Plugin: Admin script loaded');
32+
33+
// Find all enhanced greeting elements in admin
34+
const greetings = document.querySelectorAll('.pmf-plugin-enhanced-greeting');
35+
36+
greetings.forEach((greeting) => {
37+
// Add subtle admin-specific interaction
38+
greeting.addEventListener('mouseenter', function () {
39+
this.style.borderLeftWidth = '6px';
40+
});
41+
42+
greeting.addEventListener('mouseleave', function () {
43+
this.style.borderLeftWidth = '4px';
44+
});
45+
});
46+
}
47+
})();
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Enhanced Example Plugin - Frontend JavaScript
3+
*
4+
* This script demonstrates how plugins can provide their own JavaScript
5+
* functionality for frontend pages.
6+
*
7+
* This Source Code Form is subject to the terms of the Mozilla Public License,
8+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
9+
* obtain one at https://mozilla.org/MPL/2.0/.
10+
*
11+
* @package phpMyFAQ
12+
* @author Thorsten Rinne <thorsten@phpmyfaq.de>
13+
* @copyright 2025 phpMyFAQ Team
14+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
15+
* @link https://www.phpmyfaq.de
16+
* @since 2025-12-28
17+
*/
18+
19+
/* global document, console, setTimeout */
20+
21+
(function () {
22+
'use strict';
23+
24+
// Wait for DOM to be ready
25+
if (document.readyState === 'loading') {
26+
document.addEventListener('DOMContentLoaded', init);
27+
} else {
28+
init();
29+
}
30+
31+
function init() {
32+
console.log('Enhanced Example Plugin: Frontend script loaded');
33+
34+
// Find all enhanced greeting elements
35+
const greetings = document.querySelectorAll('.pmf-plugin-enhanced-greeting');
36+
37+
greetings.forEach((greeting) => {
38+
// Add a click handler for interactivity
39+
greeting.addEventListener('click', function () {
40+
this.style.transform = 'scale(0.98)';
41+
setTimeout(() => {
42+
this.style.transform = '';
43+
}, 150);
44+
});
45+
46+
// Add hover effect
47+
greeting.style.cursor = 'pointer';
48+
greeting.title = 'Click me!';
49+
});
50+
}
51+
})();

phpmyfaq/content/plugins/HelloWorld/HelloWorldPlugin.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ public function getTranslationsPath(): ?string
6969
return null; // No translations for this simple plugin
7070
}
7171

72+
public function getScripts(): array
73+
{
74+
return []; // No scripts for this simple plugin
75+
}
76+
7277
public function registerEvents(EventDispatcherInterface $eventDispatcher): void
7378
{
7479
$eventDispatcher->addListener('hello.world', [$this, 'onContentLoaded']);

phpmyfaq/content/plugins/ReadingTime/ReadingTimePlugin.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ public function getTranslationsPath(): ?string
105105
return null; // No translations for this plugin
106106
}
107107

108+
/**
109+
* @inheritDoc
110+
*/
111+
public function getScripts(): array
112+
{
113+
return []; // No scripts for this plugin
114+
}
115+
108116
/**
109117
* @inheritDoc
110118
*/

phpmyfaq/index.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@
520520
'copyright' => System::getPoweredByString(),
521521
'isUserRegistrationEnabled' => $faqConfig->get('security.enableRegistration'),
522522
'pluginStylesheets' => $faqConfig->getPluginManager()->getAllPluginStylesheets(),
523+
'pluginScripts' => $faqConfig->getPluginManager()->getAllPluginScripts(),
523524
'msgRegisterUser' => Translation::get(key: 'msgRegisterUser'),
524525
'sendPassword' =>
525526
'<a href="'

phpmyfaq/src/phpMyFAQ/Controller/Administration/AbstractAdministrationController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ protected function getHeader(Request $request): array
8484
item: 'security.enableAdminSessionTimeoutCounter',
8585
),
8686
'pluginStylesheets' => $this->configuration->getPluginManager()->getAllPluginStylesheets(),
87+
'pluginScripts' => $this->configuration->getPluginManager()->getAllPluginScripts(),
8788
] + $pageFlags;
8889
}
8990

0 commit comments

Comments
 (0)