mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-19 04:51:17 +00:00
docs(plugins): add experimental plugins section + plugins:create command to create plugin via CLI
This commit is contained in:
parent
91dc8c8325
commit
8f8c61eaae
@ -2,7 +2,7 @@
|
|||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": "*.md",
|
"files": ["*.md", "*.mdx"],
|
||||||
"options": {
|
"options": {
|
||||||
"proseWrap": "always"
|
"proseWrap": "always"
|
||||||
}
|
}
|
||||||
|
@ -183,6 +183,35 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Plugins",
|
||||||
|
badge: {
|
||||||
|
text: "Experimental",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "Introduction",
|
||||||
|
link: "/plugins/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Creating a plugin",
|
||||||
|
link: "/plugins/create",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Reference",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "manifest.json",
|
||||||
|
link: "/plugins/manifest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "hooks",
|
||||||
|
link: "/plugins/hooks",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
editLink: {
|
editLink: {
|
||||||
baseUrl:
|
baseUrl:
|
||||||
|
56
docs/src/content/docs/en/plugins/create.mdx
Normal file
56
docs/src/content/docs/en/plugins/create.mdx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: Creating a Plugin
|
||||||
|
---
|
||||||
|
|
||||||
|
import { FileTree, Steps } from "@astrojs/starlight/components";
|
||||||
|
|
||||||
|
In order to get started, you first need to
|
||||||
|
[setup your Castopod dev environment](https://code.castopod.org/adaures/castopod/-/blob/develop/CONTRIBUTING-DEV.md).
|
||||||
|
|
||||||
|
## Using the create command
|
||||||
|
|
||||||
|
To quickly get you started, you can create a plugin using the following CLI
|
||||||
|
command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
php spark plugins:create
|
||||||
|
```
|
||||||
|
|
||||||
|
👉 Follow the CLI instructions: you will be prompted for metadata and hooks
|
||||||
|
definitions to generate the [plugin folder](./#plugin-folder-structure) for you.
|
||||||
|
|
||||||
|
## Manual setup
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
1. create a plugin folder inside a vendor directory
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
- plugins
|
||||||
|
- acme
|
||||||
|
- **hello-world/**
|
||||||
|
- …
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
2. add a manifest.json file
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- hello-world
|
||||||
|
- **manifest.json**
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
See the [manifest reference](./manifest).
|
||||||
|
|
||||||
|
3. add the Plugin.php class
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- hello-world
|
||||||
|
- manifest.json
|
||||||
|
- **Plugin.php**
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
</Steps>
|
3
docs/src/content/docs/en/plugins/helpers.mdx
Normal file
3
docs/src/content/docs/en/plugins/helpers.mdx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: BasePlugin
|
||||||
|
---
|
61
docs/src/content/docs/en/plugins/hooks.mdx
Normal file
61
docs/src/content/docs/en/plugins/hooks.mdx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
title: Hooks reference
|
||||||
|
---
|
||||||
|
|
||||||
|
Hooks are methods that live in the Plugin class, they are executed in parts of
|
||||||
|
the Castopod codebase.
|
||||||
|
|
||||||
|
## List
|
||||||
|
|
||||||
|
| Hooks | Executes in |
|
||||||
|
| ---------------- | ----------- |
|
||||||
|
| rssBeforeChannel | RSS Feed |
|
||||||
|
| rssAfterChannel | RSS Feed |
|
||||||
|
| rssBeforeItem | RSS Feed |
|
||||||
|
| rssAfterItem | RSS Feed |
|
||||||
|
| siteHead | Website |
|
||||||
|
|
||||||
|
### rssBeforeChannel
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function rssBeforeChannel(Podcast $podcast): void
|
||||||
|
{
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### rssAfterChannel
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $rss): void
|
||||||
|
{
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### rssBeforeItem
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function rssBeforeItem(Episode $episode): void
|
||||||
|
{
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### rssAfterItem
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function rssAfterItem(Epsiode $episode, SimpleRSSElement $rss): void
|
||||||
|
{
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### siteHead
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function siteHead(): void
|
||||||
|
{
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
```
|
133
docs/src/content/docs/en/plugins/index.mdx
Normal file
133
docs/src/content/docs/en/plugins/index.mdx
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
---
|
||||||
|
title: Castopod Plugins
|
||||||
|
---
|
||||||
|
|
||||||
|
import { FileTree, Aside } from "@astrojs/starlight/components";
|
||||||
|
|
||||||
|
Plugins are ways to extend Castopod's core features.
|
||||||
|
|
||||||
|
## Plugin folder structure
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- hello-world
|
||||||
|
- i18n
|
||||||
|
- en.json
|
||||||
|
- fr.json
|
||||||
|
- …
|
||||||
|
- icon.svg
|
||||||
|
- [manifest.json](./manifest) // required
|
||||||
|
- [Plugin.php](#plugin-class) // required
|
||||||
|
- README.md
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
Plugins reside in the `plugins` folder under a **vendor** folder, ie. the
|
||||||
|
organisation or person who authored the plugin.
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- **plugins**
|
||||||
|
- acme
|
||||||
|
- hello-world/
|
||||||
|
- …
|
||||||
|
- atlantis/
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
### manifest.json (required)
|
||||||
|
|
||||||
|
The plugin manifest is a JSON file containing your plugin's metadata and
|
||||||
|
permissions.
|
||||||
|
|
||||||
|
This file will determine whether a plugin is valid or not. The minimal required
|
||||||
|
data being:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "acme/hello-world",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Checkout the [manifest.json reference](./manifest).
|
||||||
|
|
||||||
|
<h3 id="plugin-class">Plugin class (required)</h3>
|
||||||
|
|
||||||
|
This is where your plugin's logic will live.
|
||||||
|
|
||||||
|
The Plugin class must extend Castopod's BasePlugin class and implement one or
|
||||||
|
multiple [Hooks](./hooks) (methods).
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Plugin.php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Modules\Plugins\Core\BasePlugin;
|
||||||
|
|
||||||
|
class AcmeHelloWorldPlugin extends BasePlugin
|
||||||
|
{
|
||||||
|
// …
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
|
||||||
|
The Plugin class name is determined by its `vendor/name` pair.
|
||||||
|
For example, a plugin living under the `acme/hello-world` folder must be named
|
||||||
|
`AcmeHelloWorldPlugin`:
|
||||||
|
|
||||||
|
- the first letter of every word is capitalized (ie. PascalCase)
|
||||||
|
- any special caracter is removed
|
||||||
|
- the `Plugin` suffix is added
|
||||||
|
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
### README.md
|
||||||
|
|
||||||
|
The `README.md` file is loaded into the plugin's view page for the user to
|
||||||
|
read.
|
||||||
|
It should be used for any additional information to help guide the user in using
|
||||||
|
the plugin.
|
||||||
|
|
||||||
|
### icon.svg
|
||||||
|
|
||||||
|
The plugin icon is displayed next to its title, it is an SVG file intended to
|
||||||
|
give a graphical representation of the plugin.
|
||||||
|
|
||||||
|
The icon should be squared, and be legible in a 64px by 64px circle.
|
||||||
|
|
||||||
|
### Internationalization (i18n)
|
||||||
|
|
||||||
|
Translation strings live under the `i18n` folder. Translation files are JSON
|
||||||
|
files named as locale keys:
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- **i18n**
|
||||||
|
- en.json // default locale
|
||||||
|
- fr.json
|
||||||
|
- de.json
|
||||||
|
- …
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
Supported locales are:
|
||||||
|
`br`,`ca`,`de`,`en`,`es`,`fr`,`nn-no`,`pl`,`pt-br`,`sr-latn`,`zh-hans`.
|
||||||
|
|
||||||
|
The translation strings allow you to translate the title, description and
|
||||||
|
settings keys.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "Hello, World!",
|
||||||
|
"description": "A Castopod plugin to greet the world!",
|
||||||
|
"settings": {
|
||||||
|
"general": {},
|
||||||
|
"podcast": {},
|
||||||
|
"episode": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
59
docs/src/content/docs/en/plugins/manifest.mdx
Normal file
59
docs/src/content/docs/en/plugins/manifest.mdx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
title: manifest.json reference
|
||||||
|
---
|
||||||
|
|
||||||
|
This page details the attributes of a Castopod Plugin's manifest, which must be
|
||||||
|
a JSON file.
|
||||||
|
|
||||||
|
### name (required)
|
||||||
|
|
||||||
|
The plugin name, including 'vendor-name/' prefix.
|
||||||
|
|
||||||
|
### version (required)
|
||||||
|
|
||||||
|
The plugin's semantic version (eg. 1.0.0) - see https://semver.org/
|
||||||
|
|
||||||
|
### description
|
||||||
|
|
||||||
|
The plugin's description. This helps people discover your plugin as it's listed
|
||||||
|
in repositories
|
||||||
|
|
||||||
|
### authors
|
||||||
|
|
||||||
|
Array one or more persons having authored the plugin. A person is an object with
|
||||||
|
a required "name" field and optional "email" and "url" fields:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Jean D'eau",
|
||||||
|
"email": "jean.deau@example.com",
|
||||||
|
"url": "https://example.com/"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or you can shorten the object into a single string:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"Jean D'eau <jean.deau@example.com> (https://example.com/)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### homepage
|
||||||
|
|
||||||
|
The URL to the project homepage.
|
||||||
|
|
||||||
|
### license
|
||||||
|
|
||||||
|
You should specify a license for your plugin so that people know how they are
|
||||||
|
permitted to use it, and any restrictions you're placing on it.
|
||||||
|
|
||||||
|
### private
|
||||||
|
|
||||||
|
### keywords
|
||||||
|
|
||||||
|
### hooks
|
||||||
|
|
||||||
|
### settings
|
||||||
|
|
||||||
|
### files
|
||||||
|
|
||||||
|
### repository
|
159
modules/Plugins/Commands/CreatePlugin.php
Normal file
159
modules/Plugins/Commands/CreatePlugin.php
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Modules\Plugins\Commands;
|
||||||
|
|
||||||
|
use CodeIgniter\CLI\BaseCommand;
|
||||||
|
use CodeIgniter\CLI\CLI;
|
||||||
|
use Exception;
|
||||||
|
use Modules\Plugins\Config\Plugins as PluginsConfig;
|
||||||
|
use Modules\Plugins\Core\Plugins;
|
||||||
|
use Modules\Plugins\Manifest\Manifest;
|
||||||
|
use Override;
|
||||||
|
|
||||||
|
class CreatePlugin extends BaseCommand
|
||||||
|
{
|
||||||
|
protected const HOOKS_IMPORTS = [
|
||||||
|
'rssBeforeChannel' => ['use App\Entities\Podcast;'],
|
||||||
|
'rssAfterChannel' => ['use App\Entities\Podcast;', 'use App\Libraries\SimpleRSSElement;'],
|
||||||
|
'rssBeforeItem' => ['use App\Entities\Episode;'],
|
||||||
|
'rssAfterItem' => ['use App\Entities\Episode;', 'use App\Libraries\SimpleRSSElement;'],
|
||||||
|
'siteHead' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected const HOOKS_METHODS = [
|
||||||
|
'rssBeforeChannel' => ' public function rssBeforeChannel(Podcast $podcast): void
|
||||||
|
{
|
||||||
|
// YOUR CODE HERE
|
||||||
|
}',
|
||||||
|
'rssAfterChannel' => ' public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void
|
||||||
|
{
|
||||||
|
// YOUR CODE HERE
|
||||||
|
}',
|
||||||
|
'rssBeforeItem' => ' public function rssBeforeItem(Episode $episode): void
|
||||||
|
{
|
||||||
|
// YOUR CODE HERE
|
||||||
|
}',
|
||||||
|
'rssAfterItem' => ' public function rssAfterItem(Episode $episode, SimpleRSSElement $item): void
|
||||||
|
{
|
||||||
|
// YOUR CODE HERE
|
||||||
|
}',
|
||||||
|
'siteHead' => ' public function siteHead(): void
|
||||||
|
{
|
||||||
|
// YOUR CODE HERE
|
||||||
|
}',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $group = 'Plugins';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name = 'plugins:create';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Generates a new plugin folder based on a template.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actually execute a command.
|
||||||
|
*
|
||||||
|
* @param list<string> $params
|
||||||
|
*/
|
||||||
|
#[Override]
|
||||||
|
public function run(array $params): void
|
||||||
|
{
|
||||||
|
$pluginName = CLI::prompt(
|
||||||
|
'Plugin name (<vendor>/<name>)',
|
||||||
|
'acme/hello-world',
|
||||||
|
Manifest::VALIDATION_RULES['name']
|
||||||
|
);
|
||||||
|
CLI::newLine();
|
||||||
|
$description = CLI::prompt('Description', '', Manifest::VALIDATION_RULES['description']);
|
||||||
|
CLI::newLine();
|
||||||
|
$license = CLI::prompt('License', 'UNLICENSED', Manifest::VALIDATION_RULES['license']);
|
||||||
|
CLI::newLine();
|
||||||
|
$hooks = CLI::promptByMultipleKeys('Which hooks do you want to implement?', Plugins::HOOKS);
|
||||||
|
|
||||||
|
$nameParts = explode('/', $pluginName);
|
||||||
|
$vendor = $nameParts[0];
|
||||||
|
$name = $nameParts[1];
|
||||||
|
|
||||||
|
/** @var PluginsConfig $pluginsConfig */
|
||||||
|
$pluginsConfig = config('Plugins');
|
||||||
|
|
||||||
|
// 1. create plugin directory if not existent
|
||||||
|
$pluginDirectory = $pluginsConfig->folder . $vendor . DIRECTORY_SEPARATOR . $name;
|
||||||
|
if (! file_exists($pluginDirectory)) {
|
||||||
|
mkdir($pluginDirectory, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. get contents of templates
|
||||||
|
$manifestTemplate = file_get_contents(__DIR__ . '/plugin-template/manifest.tpl.json');
|
||||||
|
|
||||||
|
if (! $manifestTemplate) {
|
||||||
|
throw new Exception('Failed to get manifest template.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pluginClassTemplate = file_get_contents(__DIR__ . '/plugin-template/Plugin.tpl.php');
|
||||||
|
|
||||||
|
if (! $pluginClassTemplate) {
|
||||||
|
throw new Exception('Failed to get Plugin class template.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. edit templates' contents
|
||||||
|
$manifestContents = str_replace('"name": ""', '"name": "' . $pluginName . '"', $manifestTemplate);
|
||||||
|
$manifestContents = str_replace(
|
||||||
|
'"description": ""',
|
||||||
|
'"description": "' . $description . '"',
|
||||||
|
$manifestContents
|
||||||
|
);
|
||||||
|
$manifestContents = str_replace('"license": ""', '"license": "' . $license . '"', $manifestContents);
|
||||||
|
$manifestContents = str_replace(
|
||||||
|
'"hooks": []',
|
||||||
|
'"hooks": ["' . implode('", "', $hooks) . '"]',
|
||||||
|
$manifestContents
|
||||||
|
);
|
||||||
|
|
||||||
|
$pluginClassName = str_replace(
|
||||||
|
' ',
|
||||||
|
'',
|
||||||
|
ucwords(str_replace(['-', '_', '.'], ' ', $vendor . ' ' . $name)) . 'Plugin'
|
||||||
|
);
|
||||||
|
$pluginClassContents = str_replace('class Plugin', 'class ' . $pluginClassName, $pluginClassTemplate);
|
||||||
|
|
||||||
|
$allImports = [];
|
||||||
|
$allMethods = [];
|
||||||
|
foreach ($hooks as $hook) {
|
||||||
|
$allImports = [...$allImports, ...self::HOOKS_IMPORTS[$hook]];
|
||||||
|
$allMethods = [...$allMethods, self::HOOKS_METHODS[$hook]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$imports = implode(PHP_EOL, array_unique($allImports));
|
||||||
|
$methods = implode(PHP_EOL . PHP_EOL, $allMethods);
|
||||||
|
$pluginClassContents = str_replace('// IMPORTS_HERE', $imports, $pluginClassContents);
|
||||||
|
$pluginClassContents = str_replace(' // HOOKS_HERE', $methods, $pluginClassContents);
|
||||||
|
|
||||||
|
$manifest = $pluginDirectory . '/manifest.json';
|
||||||
|
$pluginClass = $pluginDirectory . '/Plugin.php';
|
||||||
|
|
||||||
|
if (! file_put_contents($manifest, $manifestContents)) {
|
||||||
|
throw new Exception('Failed to create manifest.json file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! file_put_contents($pluginClass, $pluginClassContents)) {
|
||||||
|
throw new Exception('Failed to create Plugin class file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI::newLine(1);
|
||||||
|
CLI::write(
|
||||||
|
sprintf('Plugin %s created in %s', CLI::color($pluginName, 'white'), CLI::color($pluginDirectory, 'white')),
|
||||||
|
'green'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,7 @@ class UninstallPlugin extends BaseCommand
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = '';
|
protected $description = 'Removes a plugin from the plugins directory.';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Command's Usage
|
* The Command's Usage
|
||||||
|
11
modules/Plugins/Commands/plugin-template/Plugin.tpl.php
Normal file
11
modules/Plugins/Commands/plugin-template/Plugin.tpl.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// IMPORTS_HERE
|
||||||
|
use Modules\Plugins\Core\BasePlugin;
|
||||||
|
|
||||||
|
class Plugin extends BasePlugin
|
||||||
|
{
|
||||||
|
// HOOKS_HERE
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "",
|
||||||
|
"license": "",
|
||||||
|
"hooks": []
|
||||||
|
}
|
@ -206,7 +206,7 @@ class PluginController extends BaseController
|
|||||||
|
|
||||||
return redirect()->back()
|
return redirect()->back()
|
||||||
->with('message', lang('Plugins.messages.saveSettingsSuccess', [
|
->with('message', lang('Plugins.messages.saveSettingsSuccess', [
|
||||||
'pluginName' => $plugin->getName(),
|
'pluginTitle' => $plugin->getTitle(),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,17 +233,17 @@ abstract class BasePlugin implements PluginInterface
|
|||||||
return $this->package;
|
return $this->package;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function getName(): string
|
final public function getTitle(): string
|
||||||
{
|
{
|
||||||
$key = sprintf('Plugin.%s.name', $this->key);
|
$key = sprintf('Plugin.%s.title', $this->key);
|
||||||
/** @var string $name */
|
/** @var string $title */
|
||||||
$name = lang($key);
|
$title = lang($key);
|
||||||
|
|
||||||
if ($name === $key) {
|
if ($title === $key) {
|
||||||
return $this->manifest->name;
|
return $this->manifest->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $name;
|
return $title;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function getDescription(): ?string
|
final public function getDescription(): ?string
|
||||||
|
@ -19,9 +19,9 @@ return [
|
|||||||
'declaredHooks' => 'Declared hooks',
|
'declaredHooks' => 'Declared hooks',
|
||||||
'settings' => 'Settings',
|
'settings' => 'Settings',
|
||||||
'settingsTitle' => '{type, select,
|
'settingsTitle' => '{type, select,
|
||||||
podcast {{pluginName} podcast settings}
|
podcast {{pluginTitle} podcast settings}
|
||||||
episode {{pluginName} episode settings}
|
episode {{pluginTitle} episode settings}
|
||||||
other {{pluginName} general settings}
|
other {{pluginTitle} general settings}
|
||||||
}',
|
}',
|
||||||
'view' => 'View',
|
'view' => 'View',
|
||||||
'activate' => 'Activate',
|
'activate' => 'Activate',
|
||||||
@ -39,7 +39,7 @@ return [
|
|||||||
'noDescription' => 'No description',
|
'noDescription' => 'No description',
|
||||||
'noReadme' => 'No README file found.',
|
'noReadme' => 'No README file found.',
|
||||||
'messages' => [
|
'messages' => [
|
||||||
'saveSettingsSuccess' => '{pluginName} settings were successfully saved!',
|
'saveSettingsSuccess' => '{pluginTitle} settings were successfully saved!',
|
||||||
],
|
],
|
||||||
'errors' => [
|
'errors' => [
|
||||||
'manifestError' => 'Plugin manifest has errors',
|
'manifestError' => 'Plugin manifest has errors',
|
||||||
|
@ -25,7 +25,7 @@ class Manifest extends ManifestObject
|
|||||||
/**
|
/**
|
||||||
* @var array<string,string>
|
* @var array<string,string>
|
||||||
*/
|
*/
|
||||||
protected const VALIDATION_RULES = [
|
public const VALIDATION_RULES = [
|
||||||
'name' => 'required|max_length[128]|regex_match[/^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*$/]',
|
'name' => 'required|max_length[128]|regex_match[/^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*$/]',
|
||||||
'version' => 'required|regex_match[/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/]',
|
'version' => 'required|regex_match[/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/]',
|
||||||
'description' => 'permit_empty|max_length[256]',
|
'description' => 'permit_empty|max_length[256]',
|
||||||
|
@ -88,7 +88,7 @@ $navigation = [
|
|||||||
foreach (plugins()->getActivePlugins() as $plugin) {
|
foreach (plugins()->getActivePlugins() as $plugin) {
|
||||||
$route = route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage());
|
$route = route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage());
|
||||||
$navigation['plugins']['items'][] = $route;
|
$navigation['plugins']['items'][] = $route;
|
||||||
$navigation['plugins']['items-labels'][$route] = $plugin->getName();
|
$navigation['plugins']['items-labels'][$route] = $plugin->getTitle();
|
||||||
$navigation['plugins']['items-permissions'][$route] = 'plugins.manage';
|
$navigation['plugins']['items-permissions'][$route] = 'plugins.manage';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ $episodeNavigation = [
|
|||||||
foreach (plugins()->getPluginsWithEpisodeSettings() as $plugin) {
|
foreach (plugins()->getPluginsWithEpisodeSettings() as $plugin) {
|
||||||
$route = route_to('plugins-settings-episode', $plugin->getVendor(), $plugin->getPackage(), $podcast->id, $episode->id);
|
$route = route_to('plugins-settings-episode', $plugin->getVendor(), $plugin->getPackage(), $podcast->id, $episode->id);
|
||||||
$episodeNavigation['plugins']['items'][] = $route;
|
$episodeNavigation['plugins']['items'][] = $route;
|
||||||
$episodeNavigation['plugins']['items-labels'][$route] = $plugin->getName();
|
$episodeNavigation['plugins']['items-labels'][$route] = $plugin->getTitle();
|
||||||
$episodeNavigation['plugins']['items-permissions'][$route] = 'episodes.edit';
|
$episodeNavigation['plugins']['items-permissions'][$route] = 'episodes.edit';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ use Modules\Plugins\Core\PluginStatus;
|
|||||||
</div>
|
</div>
|
||||||
<img class="rounded-full min-w-16 max-w-16 aspect-square" src="<?= $plugin->getIconSrc() ?>">
|
<img class="rounded-full min-w-16 max-w-16 aspect-square" src="<?= $plugin->getIconSrc() ?>">
|
||||||
<div class="flex flex-col items-start mt-2 mb-6">
|
<div class="flex flex-col items-start mt-2 mb-6">
|
||||||
<h2 class="flex items-center text-xl font-bold font-display gap-x-2" title="<?= $plugin->getName() ?>"><a class="line-clamp-1" href="<?= route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage()) ?>" class="hover:underline decoration-accent"><?= $plugin->getName() ?></a></h2>
|
<h2 class="flex items-center text-xl font-bold font-display gap-x-2" title="<?= $plugin->getTitle() ?>"><a class="line-clamp-1" href="<?= route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage()) ?>" class="hover:underline decoration-accent"><?= $plugin->getTitle() ?></a></h2>
|
||||||
<p class="inline-flex font-mono text-xs">
|
<p class="inline-flex font-mono text-xs">
|
||||||
<span class="inline-flex tracking-wide bg-gray-100">
|
<span class="inline-flex tracking-wide bg-gray-100">
|
||||||
<a href="<?= route_to('plugins-vendor', $plugin->getVendor()) ?>" class="underline underline-offset-2 decoration-2 decoration-dotted hover:decoration-solid decoration-accent"><?= $plugin->getVendor() ?></a>
|
<a href="<?= route_to('plugins-vendor', $plugin->getVendor()) ?>" class="underline underline-offset-2 decoration-2 decoration-dotted hover:decoration-solid decoration-accent"><?= $plugin->getVendor() ?></a>
|
||||||
|
@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
<?= $this->section('title') ?>
|
<?= $this->section('title') ?>
|
||||||
<?= lang('Plugins.settingsTitle', [
|
<?= lang('Plugins.settingsTitle', [
|
||||||
'pluginName' => $plugin->getName(),
|
'pluginTitle' => $plugin->getTitle(),
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
]) ?>
|
]) ?>
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
<?= $this->section('pageTitle') ?>
|
<?= $this->section('pageTitle') ?>
|
||||||
<?= lang('Plugins.settingsTitle', [
|
<?= lang('Plugins.settingsTitle', [
|
||||||
'pluginName' => $plugin->getName(),
|
'pluginTitle' => $plugin->getTitle(),
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
]) ?>
|
]) ?>
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
<?= $this->extend('_layout') ?>
|
<?= $this->extend('_layout') ?>
|
||||||
|
|
||||||
<?= $this->section('title') ?>
|
<?= $this->section('title') ?>
|
||||||
<?= $plugin->getName() ?>
|
<?= $plugin->getTitle() ?>
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
<?= $this->section('pageTitle') ?>
|
<?= $this->section('pageTitle') ?>
|
||||||
<?= $plugin->getName() ?>
|
<?= $plugin->getTitle() ?>
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
<?= $this->section('headerLeft') ?>
|
<?= $this->section('headerLeft') ?>
|
||||||
|
@ -92,7 +92,7 @@ $podcastNavigation = [
|
|||||||
foreach (plugins()->getPluginsWithPodcastSettings() as $plugin) {
|
foreach (plugins()->getPluginsWithPodcastSettings() as $plugin) {
|
||||||
$route = route_to('plugins-settings-podcast', $plugin->getVendor(), $plugin->getPackage(), $podcast->id);
|
$route = route_to('plugins-settings-podcast', $plugin->getVendor(), $plugin->getPackage(), $podcast->id);
|
||||||
$podcastNavigation['plugins']['items'][] = $route;
|
$podcastNavigation['plugins']['items'][] = $route;
|
||||||
$podcastNavigation['plugins']['items-labels'][$route] = $plugin->getName();
|
$podcastNavigation['plugins']['items-labels'][$route] = $plugin->getTitle();
|
||||||
$podcastNavigation['plugins']['items-permissions'][$route] = 'edit';
|
$podcastNavigation['plugins']['items-permissions'][$route] = 'edit';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user