mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
* use Blade view and Eloquent models for plugins * move views * fix style * fix style * revert mistake * Update Plugin.php delete test property "name" * rename plugin function to settings * last but not least - rename in Test.php * Rename Test to Example * fix typo * fix style * fix style * fix style * fix style - I hate tabs... * Extract view calls * fix method calls and style * Move Models the the abstract class * fix style * Convert to traits * Change the Example description * Fix style * Fix style * Fix style * Convert plugin function to Model static methods and delete .inc.php * fix style * fix style * Use scope * final methods blows up legacy code * Config > \LibreNMS\Config * convert the static string to a static method * Correct placement in the page * fix tabs * fix style * Rename from tait to hook to make it easier to understand and be complient * rename file * Typo * Started to change the docu * change to a more usefully Device_Overview example * and activate of course * PluginManager * fix .gitignore * only php files in the root folder * corrected .gitignore with all files :) * Rename the Hooks and ExampleClass for better readability * Fix style * Fix style * Exception handling (especially if DB is not present) * Fix style and update schema * fix indentation * actually correct indent * fix migration collation check include utf8mb4_bin * stop phpstan whining * A view lines documentation * add typeHints * Allow return null on handle * lint * fix return types * fix logic of column collation check * Fix MenuEntryHook * switch to longtext instead of json type for now :D * try phpstan on PHP 7.3 * set phpstan target version to 7.3 * all the typehints * optional * more * Use namespace to prevent view collisions disambiguate plugin and hook no magic guessing of names in PluginManager, bad assumptions remove unused plugins from the DB * cleanup plugin menu * cleanup on shutdown and ignore but log query error on cleanup * instanceof must be called against an instance * Allow multiple hooks per plugin * Port plugin ui code to Laravel * page instead of settings for v1 plugins * actually working settings pages a little url cleanup plugin/admin -> plugin/settings * fix style * Add page hook * PHPstan * Try to fix Illuminate\Http\RedirectResponse * typehint * Rewrite the doc * Fix style Co-authored-by: PipoCanaja <38363551+PipoCanaja@users.noreply.github.com> Co-authored-by: Tony Murray <murraytony@gmail.com>
257 lines
7.7 KiB
PHP
257 lines
7.7 KiB
PHP
<?php
|
|
/*
|
|
* PluginManager.php
|
|
*
|
|
* -Description-
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* @package LibreNMS
|
|
* @link http://librenms.org
|
|
* @copyright 2021 Tony Murray
|
|
* @author Tony Murray <murraytony@gmail.com>
|
|
*/
|
|
|
|
namespace App\Plugins;
|
|
|
|
use App\Exceptions\PluginException;
|
|
use App\Models\Plugin;
|
|
use Exception;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Support\Collection;
|
|
use Log;
|
|
|
|
class PluginManager
|
|
{
|
|
/** @var Collection */
|
|
private $hooks;
|
|
/** @var Collection */
|
|
private $plugins;
|
|
|
|
/** @var array */
|
|
private $validPlugins = [];
|
|
|
|
public function __construct()
|
|
{
|
|
$this->hooks = new Collection;
|
|
}
|
|
|
|
/**
|
|
* Publish plugin hook, this is the main way to hook into different parts of LibreNMS.
|
|
* plugin_name should be unique. For internal (user) plugins in the app/Plugins directory, the directory name will be used.
|
|
* Hook type will be the full class name of the hook from app/Plugins/Hooks.
|
|
*
|
|
* @param string $pluginName
|
|
* @param string $hookType
|
|
* @param string $implementationClass
|
|
* @return bool
|
|
*/
|
|
public function publishHook(string $pluginName, string $hookType, string $implementationClass): bool
|
|
{
|
|
try {
|
|
$instance = new $implementationClass;
|
|
$this->validPlugins[$pluginName] = 1;
|
|
|
|
if ($instance instanceof $hookType && $this->pluginEnabled($pluginName)) {
|
|
if (! $this->hooks->has($hookType)) {
|
|
$this->hooks->put($hookType, new Collection);
|
|
}
|
|
|
|
$this->hooks->get($hookType)->push([
|
|
'plugin_name' => $pluginName,
|
|
'instance' => $instance,
|
|
]);
|
|
|
|
return true;
|
|
}
|
|
} catch (Exception $e) {
|
|
Log::error("Error when loading hook $implementationClass of type $hookType for $pluginName: " . $e->getMessage());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if there are any valid hooks
|
|
*
|
|
* @param string $hookType
|
|
* @param array $args
|
|
* @param string|null $plugin only for this plugin if set
|
|
* @return bool
|
|
*/
|
|
public function hasHooks(string $hookType, array $args = [], ?string $plugin = null): bool
|
|
{
|
|
return $this->hooksFor($hookType, $args, $plugin)->isNotEmpty();
|
|
}
|
|
|
|
/**
|
|
* Coll all hooks for the given hook type.
|
|
* args will be available for injection into the handle method to pass data through
|
|
* settings is automatically injected
|
|
*
|
|
* @param string $hookType
|
|
* @param array $args
|
|
* @param string|null $plugin only for this plugin if set
|
|
* @return \Illuminate\Support\Collection
|
|
*/
|
|
public function call(string $hookType, array $args = [], ?string $plugin = null): Collection
|
|
{
|
|
try {
|
|
return $this->hooksFor($hookType, $args, $plugin)
|
|
->map(function ($hook) use ($args) {
|
|
return app()->call([$hook['instance'], 'handle'], $this->fillArgs($args, $hook['plugin_name']));
|
|
});
|
|
} catch (Exception $e) {
|
|
Log::error("Error calling hook $hookType: " . $e->getMessage());
|
|
|
|
return new Collection;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the settings stored in the database for a plugin.
|
|
* One plugin shares the settings across all hooks
|
|
*
|
|
* @param string $pluginName
|
|
* @return array
|
|
*/
|
|
public function getSettings(string $pluginName): array
|
|
{
|
|
return (array) $this->getPlugin($pluginName)->settings;
|
|
}
|
|
|
|
/**
|
|
* Save settings array to the database for the given plugin
|
|
*
|
|
* @param string $pluginName
|
|
* @param array $settings
|
|
* @return bool
|
|
*/
|
|
public function setSettings(string $pluginName, array $settings): bool
|
|
{
|
|
$plugin = $this->getPlugin($pluginName);
|
|
$plugin->settings = $settings;
|
|
|
|
return $plugin->save();
|
|
}
|
|
|
|
/**
|
|
* Check if plugin exists.
|
|
* Does not create a DB entry if it does not exist.
|
|
*
|
|
* @param string $pluginName
|
|
* @return bool
|
|
*/
|
|
public function pluginExists(string $pluginName): bool
|
|
{
|
|
return $this->getPlugins()->has($pluginName);
|
|
}
|
|
|
|
/**
|
|
* Check if plugin of the given name is enabled.
|
|
* Creates DB entry if one does not exist yet.
|
|
*
|
|
* @param string $pluginName
|
|
* @return bool
|
|
*/
|
|
public function pluginEnabled(string $pluginName): bool
|
|
{
|
|
return $this->getPlugin($pluginName)->plugin_active;
|
|
}
|
|
|
|
/**
|
|
* Remove plugins that do not have any registered hooks.
|
|
*/
|
|
public function cleanupPlugins(): void
|
|
{
|
|
try {
|
|
$valid = array_keys($this->validPlugins);
|
|
Plugin::versionTwo()->whereNotIn('plugin_name', $valid)->get()->each->delete();
|
|
} catch (QueryException $qe) {
|
|
Log::error('Failed to clean up plugins: ' . $qe->getMessage());
|
|
}
|
|
}
|
|
|
|
protected function getPlugin(string $name): ?Plugin
|
|
{
|
|
$plugin = $this->getPlugins()->get($name);
|
|
|
|
if (! $plugin) {
|
|
try {
|
|
$plugin = Plugin::create([
|
|
'plugin_name' => $name,
|
|
'plugin_active' => 1,
|
|
'version' => 2,
|
|
]);
|
|
$this->getPlugins()->put($name, $plugin);
|
|
} catch (QueryException $e) {
|
|
// DB not migrated/connected
|
|
}
|
|
}
|
|
|
|
return $plugin;
|
|
}
|
|
|
|
protected function getPlugins(): Collection
|
|
{
|
|
if ($this->plugins === null) {
|
|
try {
|
|
$this->plugins = Plugin::versionTwo()->get()->keyBy('plugin_name');
|
|
} catch (QueryException $e) {
|
|
// DB not migrated/connected
|
|
$this->plugins = new Collection;
|
|
}
|
|
}
|
|
|
|
return $this->plugins;
|
|
}
|
|
|
|
/**
|
|
* @param string $hookType
|
|
* @param array $args
|
|
* @param string|null $onlyPlugin
|
|
* @return \Illuminate\Support\Collection
|
|
*/
|
|
protected function hooksFor(string $hookType, array $args, ?string $onlyPlugin): Collection
|
|
{
|
|
if (! $this->hooks->has($hookType)) {
|
|
return new Collection;
|
|
}
|
|
|
|
return $this->hooks->get($hookType)
|
|
->when($onlyPlugin, function (Collection $hooks, $only) {
|
|
return $hooks->where('plugin_name', $only);
|
|
})
|
|
->filter(function ($hook) use ($args) {
|
|
return app()->call([$hook['instance'], 'authorize'], $this->fillArgs($args, $hook['plugin_name']));
|
|
});
|
|
}
|
|
|
|
protected function fillArgs(array $args, string $pluginName): array
|
|
{
|
|
if (isset($args['settings'])) {
|
|
throw new PluginException('You cannot inject "settings", this is a reserved name');
|
|
}
|
|
|
|
if (isset($args['pluginName'])) {
|
|
throw new PluginException('You cannot inject "pluginName", this is a reserved name');
|
|
}
|
|
|
|
return array_merge($args, [
|
|
'pluginName' => $pluginName,
|
|
'settings' => $this->getSettings($pluginName),
|
|
]);
|
|
}
|
|
}
|