init
This commit is contained in:
341
kirby/src/Cms/Plugin.php
Normal file
341
kirby/src/Cms/Plugin.php
Normal file
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Cms;
|
||||
|
||||
use Composer\InstalledVersions;
|
||||
use Exception;
|
||||
use Kirby\Cms\System\UpdateStatus;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Exception\InvalidArgumentException;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Kirby\Toolkit\V;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Represents a Plugin and handles parsing of
|
||||
* the composer.json. It also creates the prefix
|
||||
* and media url for the plugin.
|
||||
*
|
||||
* @package Kirby Cms
|
||||
* @author Bastian Allgeier <bastian@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://getkirby.com/license
|
||||
*/
|
||||
class Plugin
|
||||
{
|
||||
protected PluginAssets $assets;
|
||||
protected UpdateStatus|null $updateStatus = null;
|
||||
|
||||
/**
|
||||
* @param string $name Plugin name within Kirby (`vendor/plugin`)
|
||||
* @param array $extends Associative array of plugin extensions
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException If the plugin name has an invalid format
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $name,
|
||||
protected array $extends = [],
|
||||
protected array $info = [],
|
||||
protected string|null $root = null,
|
||||
protected string|null $version = null,
|
||||
) {
|
||||
static::validateName($name);
|
||||
|
||||
// TODO: Remove in v7
|
||||
if ($root = $extends['root'] ?? null) {
|
||||
Helpers::deprecated('Plugin "' . $name . '": Passing the `root` inside the `extends` array has been deprecated. Pass it directly as named argument `root`.', 'plugin-extends-root');
|
||||
$this->root ??= $root;
|
||||
unset($this->extends['root']);
|
||||
}
|
||||
|
||||
$this->root ??= dirname(debug_backtrace()[0]['file']);
|
||||
|
||||
// TODO: Remove in v7
|
||||
if ($info = $extends['info'] ?? null) {
|
||||
Helpers::deprecated('Plugin "' . $name . '": Passing an `info` array inside the `extends` array has been deprecated. Pass the individual entries directly as named `info` argument.', 'plugin-extends-root');
|
||||
|
||||
if (empty($info) === false && is_array($info) === true) {
|
||||
$this->info = [...$info, ...$this->info];
|
||||
}
|
||||
|
||||
unset($this->extends['info']);
|
||||
}
|
||||
|
||||
// read composer.json and use as info fallback
|
||||
try {
|
||||
$info = Data::read($this->manifest());
|
||||
} catch (Exception) {
|
||||
// there is no manifest file or it is invalid
|
||||
$info = [];
|
||||
}
|
||||
|
||||
$this->info = [...$info, ...$this->info];
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to any composer.json field by method call
|
||||
*/
|
||||
public function __call(string $key, array $arguments = null): mixed
|
||||
{
|
||||
return $this->info()[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin asset object for a specific asset
|
||||
*/
|
||||
public function asset(string $path): PluginAsset|null
|
||||
{
|
||||
return $this->assets()->get($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin assets collection
|
||||
*/
|
||||
public function assets(): PluginAssets
|
||||
{
|
||||
return $this->assets ??= PluginAssets::factory($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array with author information
|
||||
* from the composer.json file
|
||||
*/
|
||||
public function authors(): array
|
||||
{
|
||||
return $this->info()['authors'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma-separated list with all author names
|
||||
*/
|
||||
public function authorsNames(): string
|
||||
{
|
||||
$names = [];
|
||||
|
||||
foreach ($this->authors() as $author) {
|
||||
$names[] = $author['name'] ?? null;
|
||||
}
|
||||
|
||||
return implode(', ', array_filter($names));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the associative array of extensions the plugin bundles
|
||||
*/
|
||||
public function extends(): array
|
||||
{
|
||||
return $this->extends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique ID for the plugin
|
||||
* (alias for the plugin name)
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
return $this->name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the info data (from composer.json)
|
||||
*/
|
||||
public function info(): array
|
||||
{
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current $kirby instance
|
||||
*/
|
||||
public function kirby(): App
|
||||
{
|
||||
return App::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link to the plugin homepage
|
||||
*/
|
||||
public function link(): string|null
|
||||
{
|
||||
$info = $this->info();
|
||||
$homepage = $info['homepage'] ?? null;
|
||||
$docs = $info['support']['docs'] ?? null;
|
||||
$source = $info['support']['source'] ?? null;
|
||||
|
||||
$link = $homepage ?? $docs ?? $source;
|
||||
|
||||
return V::url($link) ? $link : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the plugin's composer.json
|
||||
*/
|
||||
public function manifest(): string
|
||||
{
|
||||
return $this->root() . '/composer.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root where plugin assets are copied to
|
||||
*/
|
||||
public function mediaRoot(): string
|
||||
{
|
||||
return App::instance()->root('media') . '/plugins/' . $this->name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base URL for plugin assets
|
||||
*/
|
||||
public function mediaUrl(): string
|
||||
{
|
||||
return App::instance()->url('media') . '/plugins/' . $this->name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin name (`vendor/plugin`)
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Kirby option value for this plugin
|
||||
*/
|
||||
public function option(string $key)
|
||||
{
|
||||
return $this->kirby()->option($this->prefix() . '.' . $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the option prefix (`vendor.plugin`)
|
||||
*/
|
||||
public function prefix(): string
|
||||
{
|
||||
return str_replace('/', '.', $this->name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root where the plugin files are stored
|
||||
*/
|
||||
public function root(): string
|
||||
{
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available plugin metadata
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'authors' => $this->authors(),
|
||||
'description' => $this->description(),
|
||||
'name' => $this->name(),
|
||||
'license' => $this->license(),
|
||||
'link' => $this->link(),
|
||||
'root' => $this->root(),
|
||||
'version' => $this->version()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the update status object unless the
|
||||
* update check has been disabled for the plugin
|
||||
* @since 3.8.0
|
||||
*
|
||||
* @param array|null $data Custom override for the getkirby.com update data
|
||||
*/
|
||||
public function updateStatus(array|null $data = null): UpdateStatus|null
|
||||
{
|
||||
if ($this->updateStatus !== null) {
|
||||
return $this->updateStatus;
|
||||
}
|
||||
|
||||
$kirby = $this->kirby();
|
||||
$option = $kirby->option('updates.plugins');
|
||||
|
||||
// specific configuration per plugin
|
||||
if (is_array($option) === true) {
|
||||
// filter all option values by glob match
|
||||
$option = A::filter(
|
||||
$option,
|
||||
fn ($value, $key) => fnmatch($key, $this->name()) === true
|
||||
);
|
||||
|
||||
// sort the matches by key length (with longest key first)
|
||||
$keys = array_map('strlen', array_keys($option));
|
||||
array_multisort($keys, SORT_DESC, $option);
|
||||
|
||||
if (count($option) > 0) {
|
||||
// use the first and therefore longest key (= most specific match)
|
||||
$option = reset($option);
|
||||
} else {
|
||||
// fallback to the default option value
|
||||
$option = true;
|
||||
}
|
||||
}
|
||||
|
||||
$option ??= $kirby->option('updates') ?? true;
|
||||
|
||||
if ($option !== true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->updateStatus = new UpdateStatus($this, false, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the name follows the required pattern
|
||||
* and throws an exception if not
|
||||
*
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*/
|
||||
public static function validateName(string $name): void
|
||||
{
|
||||
if (preg_match('!^[a-z0-9-]+\/[a-z0-9-]+$!i', $name) !== 1) {
|
||||
throw new InvalidArgumentException('The plugin name must follow the format "a-z0-9-/a-z0-9-"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the normalized version number
|
||||
* from the composer.json file
|
||||
*/
|
||||
public function version(): string|null
|
||||
{
|
||||
$name = $this->info()['name'] ?? null;
|
||||
|
||||
try {
|
||||
// try to get version from "vendor/composer/installed.php",
|
||||
// this is the most reliable source for the version
|
||||
$version = InstalledVersions::getPrettyVersion($name);
|
||||
} catch (Throwable) {
|
||||
$version = null;
|
||||
}
|
||||
|
||||
// fallback to the version provided in the plugin's index.php: as named
|
||||
// argument, entry in the info array or from the composer.json file
|
||||
$version ??= $this->version ?? $this->info()['version'] ?? null;
|
||||
|
||||
if (
|
||||
is_string($version) !== true ||
|
||||
$version === '' ||
|
||||
Str::endsWith($version, '+no-version-set')
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// normalize the version number to be without leading `v`
|
||||
$version = ltrim($version, 'vV');
|
||||
|
||||
// ensure that the version number now starts with a digit
|
||||
if (preg_match('/^[0-9]/', $version) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user