mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
New plugin system based on Laravel Package Development (#12998)
* 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>
This commit is contained in:
9
app/Plugins/.gitignore
vendored
Normal file
9
app/Plugins/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except these
|
||||
!/.gitignore
|
||||
!/*.php
|
||||
!/Hooks/
|
||||
!/Hooks/**
|
||||
!/ExamplePlugin/
|
||||
!/ExamplePlugin/**
|
32
app/Plugins/ExamplePlugin/DeviceOverview.php
Normal file
32
app/Plugins/ExamplePlugin/DeviceOverview.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/*
|
||||
* ExampleSettingsPlugin.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\ExamplePlugin;
|
||||
|
||||
use App\Plugins\Hooks\DeviceOverviewHook;
|
||||
|
||||
class DeviceOverview extends DeviceOverviewHook
|
||||
{
|
||||
}
|
9
app/Plugins/ExamplePlugin/Menu.php
Normal file
9
app/Plugins/ExamplePlugin/Menu.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Plugins\ExamplePlugin;
|
||||
|
||||
use App\Plugins\Hooks\MenuEntryHook;
|
||||
|
||||
class Menu extends MenuEntryHook
|
||||
{
|
||||
}
|
32
app/Plugins/ExamplePlugin/Page.php
Normal file
32
app/Plugins/ExamplePlugin/Page.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/*
|
||||
* ExampleSettingsPlugin.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\ExamplePlugin;
|
||||
|
||||
use App\Plugins\Hooks\PageHook;
|
||||
|
||||
class Page extends PageHook
|
||||
{
|
||||
}
|
9
app/Plugins/ExamplePlugin/PortTab.php
Normal file
9
app/Plugins/ExamplePlugin/PortTab.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Plugins\ExamplePlugin;
|
||||
|
||||
use App\Plugins\Hooks\PortTabHook;
|
||||
|
||||
class PortTab extends PortTabHook
|
||||
{
|
||||
}
|
32
app/Plugins/ExamplePlugin/Settings.php
Normal file
32
app/Plugins/ExamplePlugin/Settings.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/*
|
||||
* ExampleSettingsPlugin.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\ExamplePlugin;
|
||||
|
||||
use App\Plugins\Hooks\SettingsHook;
|
||||
|
||||
class Settings extends SettingsHook
|
||||
{
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default panel-condensed">
|
||||
<div class="panel-heading">
|
||||
<strong>{{ $title }}</strong> <a href="{{ url('device/' . $device->device_id . '/notes') }}">[EDIT]</a>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{!! Str::markdown($device->notes) !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
1
app/Plugins/ExamplePlugin/resources/views/menu.blade.php
Normal file
1
app/Plugins/ExamplePlugin/resources/views/menu.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
<a href="{{ url('plugin/ExamplePlugin') }}"><i class="fa fa-coffee fa-fw fa-lg" aria-hidden="true"></i> Example Menu</a>
|
8
app/Plugins/ExamplePlugin/resources/views/page.blade.php
Normal file
8
app/Plugins/ExamplePlugin/resources/views/page.blade.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body ">
|
||||
<div class="pull-left" style="margin-top: 5px;">
|
||||
<span style="font-size: 20px;">{{ $title }}</a></span><br>
|
||||
Description
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1 @@
|
||||
This is a port plugin tab plugin for port {{ $port->getLabel() }}
|
94
app/Plugins/ExamplePlugin/resources/views/settings.blade.php
Normal file
94
app/Plugins/ExamplePlugin/resources/views/settings.blade.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<div style="margin: 15px;">
|
||||
<h4>{{ $plugin_name }} Settings:</h4>
|
||||
|
||||
<!-- Example of free-form settings, real plugins should use specific fields -->
|
||||
<!-- All input fields should be in the settings array (settings[]) -->
|
||||
|
||||
<form method="post" style="margin: 15px">
|
||||
@csrf
|
||||
<table id="settings-table">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
@forelse($settings as $name => $value)
|
||||
<tr id="settings-row-{{ $name }}">
|
||||
<td>
|
||||
{{ $name }}
|
||||
</td>
|
||||
<td>
|
||||
<input id="value-{{ $value }}" type="text" name="settings[{{ $name }}]" value="{{ $value }}">
|
||||
<button type="button" onclick="deleteSetting('{{ $name }}')" class="delete-button"><i class="fa fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td>No settings yet</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</table>
|
||||
<div style="margin: 15px 0;">
|
||||
<input id="new-setting-name" style="display: inline-block;" type="text" placeholder="Name">
|
||||
<input id="new-setting-value" style="display: inline-block;" type="text" placeholder="Value">
|
||||
<button type="button" onclick="newSetting()">Add Setting</button>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function newSetting() {
|
||||
var name = document.getElementById('new-setting-name').value;
|
||||
var value = document.getElementById('new-setting-value').value;
|
||||
var existing = document.getElementById('value-' + name);
|
||||
|
||||
if (existing) {
|
||||
existing.value = value;
|
||||
} else {
|
||||
// insert setting
|
||||
var newValue = document.createElement('input');
|
||||
newValue.id = 'value-' + name;
|
||||
newValue.type = 'text';
|
||||
newValue.name = 'settings[' + name + ']';
|
||||
newValue.value = value;
|
||||
|
||||
var deleteButton = document.createElement('button');
|
||||
deleteButton.type = 'button';
|
||||
deleteButton.className = 'delete-button';
|
||||
deleteButton.onclick = () => deleteSetting(name);
|
||||
var deleteIcon = document.createElement('i');
|
||||
deleteIcon.className = 'fa fa-trash';
|
||||
deleteButton.appendChild(deleteIcon);
|
||||
|
||||
var row = document.createElement('tr');
|
||||
row.id = 'settings-row-' + name;
|
||||
var col1 = document.createElement('td');
|
||||
var col2 = document.createElement('td');
|
||||
col1.innerText = name;
|
||||
col2.appendChild(newValue);
|
||||
col2.appendChild(document.createTextNode(' '));
|
||||
col2.appendChild(deleteButton);
|
||||
row.appendChild(col1);
|
||||
row.appendChild(col2);
|
||||
document.getElementById('settings-table').appendChild(row);
|
||||
}
|
||||
|
||||
document.getElementById('new-setting-name').value = '';
|
||||
document.getElementById('new-setting-value').value = '';
|
||||
}
|
||||
|
||||
function deleteSetting(name) {
|
||||
document.getElementById('settings-row-' + name).remove();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#settings-table td, #settings-table th {
|
||||
padding: .2em;
|
||||
}
|
||||
.delete-button {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
</style>
|
16
app/Plugins/Hook.php
Normal file
16
app/Plugins/Hook.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Plugins;
|
||||
|
||||
interface Hook
|
||||
{
|
||||
/**
|
||||
* Will be called by the plugin manager to check if the user is authorized. Will be called with Dependency Injection.
|
||||
*/
|
||||
// public function authorize(): bool;
|
||||
|
||||
/**
|
||||
* Will be called by the plugin manager to execute this plugin at the correct time. Will be called with Dependency Injection.
|
||||
*/
|
||||
// public function handle();
|
||||
}
|
54
app/Plugins/Hooks/DeviceOverviewHook.php
Normal file
54
app/Plugins/Hooks/DeviceOverviewHook.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/*
|
||||
* DeviceHook.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\Hooks;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class DeviceOverviewHook
|
||||
{
|
||||
/** @var string */
|
||||
public $view = 'resources.views.device-overview';
|
||||
|
||||
public function authorize(User $user, Device $device, array $settings): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function data(Device $device): array
|
||||
{
|
||||
return [
|
||||
'title' => __CLASS__,
|
||||
'device' => $device,
|
||||
];
|
||||
}
|
||||
|
||||
final public function handle(string $pluginName, Device $device): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view(Str::start($this->view, "$pluginName::"), $this->data($device));
|
||||
}
|
||||
}
|
50
app/Plugins/Hooks/MenuEntryHook.php
Normal file
50
app/Plugins/Hooks/MenuEntryHook.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/*
|
||||
* PluginMenuEntry.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\Hooks;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class MenuEntryHook
|
||||
{
|
||||
/** @var string */
|
||||
public $view = 'resources.views.menu';
|
||||
|
||||
public function authorize(User $user, array $settings): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
final public function handle(string $pluginName): array
|
||||
{
|
||||
return [Str::start($this->view, "$pluginName::"), $this->data()];
|
||||
}
|
||||
}
|
53
app/Plugins/Hooks/PageHook.php
Normal file
53
app/Plugins/Hooks/PageHook.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/*
|
||||
* SettingsHook.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\Hooks;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class PageHook
|
||||
{
|
||||
/** @var string */
|
||||
public $view = 'resources.views.page';
|
||||
|
||||
public function authorize(User $user): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
final public function handle(string $pluginName): array
|
||||
{
|
||||
return array_merge([
|
||||
'settings_view' => Str::start($this->view, "$pluginName::"),
|
||||
], $this->data());
|
||||
}
|
||||
}
|
55
app/Plugins/Hooks/PortTabHook.php
Normal file
55
app/Plugins/Hooks/PortTabHook.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/*
|
||||
* PortPluginTab.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\Hooks;
|
||||
|
||||
use App\Models\Port;
|
||||
use App\Models\User;
|
||||
use App\Plugins\Hook;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class PortTabHook implements Hook
|
||||
{
|
||||
/** @var string */
|
||||
public $view = 'resources.views.port-tab';
|
||||
|
||||
public function authorize(User $user, Port $port, array $settings): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function data(Port $port): array
|
||||
{
|
||||
return [
|
||||
'title' => __CLASS__,
|
||||
'port' => $port,
|
||||
];
|
||||
}
|
||||
|
||||
final public function handle(string $pluginName, Port $port): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view(Str::start($this->view, "$pluginName::"), $this->data($port));
|
||||
}
|
||||
}
|
54
app/Plugins/Hooks/SettingsHook.php
Normal file
54
app/Plugins/Hooks/SettingsHook.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/*
|
||||
* SettingsHook.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\Hooks;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class SettingsHook
|
||||
{
|
||||
/** @var string */
|
||||
public $view = 'resources.views.settings';
|
||||
|
||||
public function authorize(User $user, array $settings): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function data(array $settings): array
|
||||
{
|
||||
return [
|
||||
'settings' => $settings,
|
||||
];
|
||||
}
|
||||
|
||||
final public function handle(string $pluginName, array $settings): array
|
||||
{
|
||||
return array_merge([
|
||||
'settings_view' => Str::start($this->view, "$pluginName::"),
|
||||
], $this->data($settings));
|
||||
}
|
||||
}
|
256
app/Plugins/PluginManager.php
Normal file
256
app/Plugins/PluginManager.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?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),
|
||||
]);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user