Skip to content

Commit 1895027

Browse files
committed
feat(plugins): added plugin configuration interface, added another example
1 parent b386ede commit 1895027

File tree

11 files changed

+227
-39
lines changed

11 files changed

+227
-39
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ This is a log of major user-visible changes in each phpMyFAQ release.
3232
- added experimental update via command line (Thorsten)
3333
- added experimental support for PHP 8.6 (Thorsten)
3434
- improved online update feature (Thorsten)
35+
- improved experimental plugin manager (Thorsten)
3536
- updated Spanish translation
3637
- updated Polish translation
3738
- updated French translation

docs/plugins.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,22 @@ The plugin directory should contain a `HelloWorldPlugin.php` file that implement
1212

1313
## 9.2 Plugin configuration
1414

15-
Plugins can have configuration options.
15+
Plugins can have configuration options, implemented via the `PluginConfigurationInterface` interface.
16+
Configuration options can be defined in the plugin configuration class with Constructor Property Promotion by adding
17+
public properties.
18+
19+
### 9.3.1 Example configuration class
20+
21+
```php
22+
class MyPluginConfiguration implements PluginConfigurationInterface
23+
{
24+
public function __construct(
25+
public int $hooraysPerMinute = 200,
26+
public bool $showIcon = true,
27+
) {
28+
}
29+
}
30+
```
1631

1732
## 9.3 Plugin development
1833

@@ -55,24 +70,22 @@ class MyPlugin implements PluginInterface
5570

5671
public function getVersion(): string
5772
{
58-
return '0.1.0';
73+
return '0.2.0';
5974
}
6075

6176
public function getDependencies(): array
6277
{
6378
return [];
6479
}
6580

66-
public function getConfig(): array
81+
public function getConfig(): ?PluginConfigurationInterface
6782
{
68-
return [
69-
'option1' => 'value1'
70-
];
83+
return null;
7184
}
7285

7386
public function registerEvents(EventDispatcherInterface $dispatcher): void
7487
{
75-
$dispatcher->addListener('content.loaded', [$this, 'onContentLoaded']);
88+
$dispatcher->addListener('hello.world', [$this, 'onContentLoaded']);
7689
$dispatcher->addListener('user.login', [$this, 'onUserLogin']);
7790
}
7891

@@ -98,7 +111,7 @@ class MyPlugin implements PluginInterface
98111
99112
<div>
100113
<h2>Content Loaded Event</h2>
101-
{{ phpMyFAQPlugin('content.loaded', 'Hello, World!') | raw }}
114+
{{ phpMyFAQPlugin('hello.world', 'Hello, World!') | raw }}
102115
</div>
103116
<div>
104117
<h2>User Login Event</h2>

phpmyfaq/content/plugins/HelloWorld/HelloWorldPlugin.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
* @since 2024-07-10
1818
*/
1919

20+
declare(strict_types=1);
21+
2022
namespace phpMyFAQ\Plugin\HelloWorld;
2123

2224
use phpMyFAQ\Plugin\PluginEvent;
2325
use phpMyFAQ\Plugin\PluginInterface;
26+
use phpMyFAQ\Plugin\PluginConfigurationInterface;
2427
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
2528

2629
class HelloWorldPlugin implements PluginInterface
2730
{
31+
2832
public function getName(): string
2933
{
3034
return 'HelloWorld';
@@ -50,20 +54,20 @@ public function getDependencies(): array
5054
return [];
5155
}
5256

53-
public function getConfig(): array
57+
public function getConfig(): ?PluginConfigurationInterface
5458
{
55-
return [];
59+
return null; // No configuration needed for this simple plugin
5660
}
5761

5862
public function registerEvents(EventDispatcherInterface $eventDispatcher): void
5963
{
60-
$eventDispatcher->addListener('content.loaded', [$this, 'onContentLoaded']);
64+
$eventDispatcher->addListener('hello.world', [$this, 'onContentLoaded']);
6165
}
6266

6367
public function onContentLoaded(PluginEvent $event): void
6468
{
6569
$content = $event->getData();
66-
$output = "phpMyFAQ says: Content Loaded: " . $content . "<br>";
70+
$output = 'phpMyFAQ says: Content Loaded: ' . $content . '<br>';
6771
$event->setOutput($output);
6872
}
6973
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
/**
4+
* The ReadingTime Plugin
5+
*
6+
* This plugin estimates and displays the reading time for FAQ articles.
7+
*
8+
* Add
9+
*
10+
* {{ phpMyFAQPlugin('reading.time', answer) | raw }}
11+
*
12+
* into the FAQ template.
13+
*
14+
* This Source Code Form is subject to the terms of the Mozilla Public License,
15+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
16+
* obtain one at https://mozilla.org/MPL/2.0/.
17+
*
18+
* @package phpMyFAQ
19+
* @author Thorsten Rinne <[email protected]>
20+
* @copyright 2024-2025 phpMyFAQ Team
21+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
22+
* @link https://www.phpmyfaq.de
23+
* @since 2024-07-10
24+
*/
25+
26+
declare(strict_types=1);
27+
28+
namespace phpMyFAQ\Plugin\ReadingTime;
29+
30+
use phpMyFAQ\Plugin\PluginInterface;
31+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
32+
33+
require_once __DIR__ . '/ReadingTimePluginConfiguration.php';
34+
35+
class ReadingTimePlugin implements PluginInterface
36+
{
37+
private ReadingTimePluginConfiguration $config;
38+
39+
public function __construct()
40+
{
41+
$this->config = new ReadingTimePluginConfiguration();
42+
}
43+
44+
/**
45+
* @inheritDoc
46+
*/
47+
public function getName(): string
48+
{
49+
return 'ReadingTimePlugin';
50+
}
51+
52+
/**
53+
* @inheritDoc
54+
*/
55+
public function getVersion(): string
56+
{
57+
return '0.2.0';
58+
}
59+
60+
/**
61+
* @inheritDoc
62+
*/
63+
public function getDescription(): string
64+
{
65+
return 'Shows estimated reading time for FAQ articles';
66+
}
67+
68+
/**
69+
* @inheritDoc
70+
*/
71+
public function getAuthor(): string
72+
{
73+
return 'phpMyFAQ Team';
74+
}
75+
76+
/**
77+
* @inheritDoc
78+
*/
79+
public function getDependencies(): array
80+
{
81+
return [];
82+
}
83+
84+
/**
85+
* @inheritDoc
86+
*/
87+
public function getConfig(): ?ReadingTimePluginConfiguration
88+
{
89+
return $this->config;
90+
}
91+
92+
/**
93+
* @inheritDoc
94+
*/
95+
public function registerEvents(EventDispatcherInterface $eventDispatcher): void
96+
{
97+
$eventDispatcher->addListener('reading.time', [$this, 'addReadingTime']);
98+
}
99+
100+
public function addReadingTime($event): void
101+
{
102+
$content = $event->getData();
103+
$readingTime = $this->calculateReadingTime($content);
104+
$badge = $this->generateBadge($readingTime);
105+
106+
$event->setOutput($badge);
107+
}
108+
109+
private function calculateReadingTime(string $content): int
110+
{
111+
$plainText = strip_tags($content);
112+
113+
$wordCount = str_word_count($plainText);
114+
115+
return max(1, (int) ceil($wordCount / $this->config->wordsPerMinute));
116+
}
117+
118+
private function generateBadge(int $minutes): string
119+
{
120+
$showIcon = $this->config->showIcon;
121+
122+
$icon = $showIcon ? '<i class="bi bi-clock"></i> ' : '';
123+
$pluralSuffix = $minutes === 1 ? '' : 'n';
124+
125+
return sprintf('%s ~ %d min %s', $icon, $minutes, $pluralSuffix);
126+
}
127+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/**
4+
* Configuration class for ReadingTime Plugin
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 <[email protected]>
12+
* @copyright 2024-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 2024-12-28
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace phpMyFAQ\Plugin\ReadingTime;
21+
22+
use phpMyFAQ\Plugin\PluginConfigurationInterface;
23+
24+
class ReadingTimePluginConfiguration implements PluginConfigurationInterface
25+
{
26+
public function __construct(
27+
public int $wordsPerMinute = 200,
28+
public bool $showIcon = true,
29+
) {
30+
}
31+
}

phpmyfaq/src/phpMyFAQ/Language/LanguageCodes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ class LanguageCodes
256256
'ar' => 'Arabic',
257257
'eu' => 'Basque',
258258
'bn' => 'Bengali',
259+
'bs' => 'Bosnian',
259260
'zh' => 'Chinese',
260261
'cs' => 'Czech',
261262
'da' => 'Danish',
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/**
4+
* The Plugin Configuration interface
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 <[email protected]>
12+
* @copyright 2024-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 2024-12-28
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace phpMyFAQ\Plugin;
21+
22+
interface PluginConfigurationInterface {}

phpmyfaq/src/phpMyFAQ/Plugin/PluginInterface.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ public function getAuthor(): string;
4949
public function getDependencies(): array;
5050

5151
/**
52-
* Returns the configuration of the plugin
52+
* Returns the configuration of the plugin (optional)
5353
*/
54-
public function getConfig(): array;
54+
public function getConfig(): ?PluginConfigurationInterface;
5555

5656
/**
5757
* Register the events

phpmyfaq/src/phpMyFAQ/Plugin/PluginManager.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class PluginManager
3333

3434
private readonly EventDispatcher $eventDispatcher;
3535

36+
/** @var PluginConfigurationInterface[] */
3637
private array $config = [];
3738

3839
/** @var string[] */
@@ -53,10 +54,10 @@ public function __construct()
5354
*/
5455
public function registerPlugin(string $pluginClass): void
5556
{
56-
$plugin = new $pluginClass($this);
57+
$plugin = new $pluginClass();
5758
if ($this->isCompatible($plugin)) {
5859
$this->plugins[$plugin->getName()] = $plugin;
59-
$this->containerBuilder->register($plugin->getName(), $pluginClass)->setArguments([$this]);
60+
$this->containerBuilder->register($plugin->getName(), $pluginClass);
6061
} else {
6162
throw new PluginException(sprintf('Plugin %s is not compatible.', $plugin->getName()));
6263
}
@@ -109,19 +110,20 @@ public function triggerEvent(string $eventName, mixed $data = null): string
109110
/**
110111
* Loads the configuration for a plugin
111112
*
112-
* @param string[] $config
113+
* @param string $pluginName
114+
* @param PluginConfigurationInterface $config
113115
*/
114-
public function loadPluginConfig(string $pluginName, array $config): void
116+
public function loadPluginConfig(string $pluginName, PluginConfigurationInterface $config): void
115117
{
116118
$this->config[$pluginName] = $config;
117119
}
118120

119121
/**
120122
* Returns the configuration for a plugin
121123
*/
122-
public function getPluginConfig(string $pluginName): array
124+
public function getPluginConfig(string $pluginName): ?PluginConfigurationInterface
123125
{
124-
return $this->config[$pluginName] ?? [];
126+
return $this->config[$pluginName] ?? null;
125127
}
126128

127129
public function getPlugins(): array

tests/phpMyFAQ/Plugin/MockPlugin.php

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,6 @@
66

77
class MockPlugin implements PluginInterface
88
{
9-
private PluginManager $manager;
10-
11-
public function __construct($manager)
12-
{
13-
$this->manager = $manager;
14-
}
159

1610
public function getName(): string
1711
{
@@ -38,11 +32,9 @@ public function getDependencies(): array
3832
return [];
3933
}
4034

41-
public function getConfig(): array
35+
public function getConfig(): ?PluginConfigurationInterface
4236
{
43-
return [
44-
'option1' => 'value1'
45-
];
37+
return null; // No configuration needed for mock plugin
4638
}
4739

4840
public function registerEvents(EventDispatcherInterface $eventDispatcher): void

0 commit comments

Comments
 (0)