Skip to content

Commit afcff42

Browse files
committed
wip
1 parent 310f06b commit afcff42

File tree

23 files changed

+1095
-214
lines changed

23 files changed

+1095
-214
lines changed

PLUGINS.md

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,33 @@ Plugins are in beta (since Release _Isfahan_) and installation or the overall AP
77

88
## Plugin structure
99

10-
Before actually creating a plugin, here is how the folder structure of your plugin **has** to be:
1110

12-
- `App`: Hosting your Laravel Models and an `info.xml` file with several metadata of your plugin
13-
- `Controllers`: Hosting your Laravel Controllers
14-
- `Migration`: Place your migration files here
15-
- `js`: This is the destination folder for your Vue/JavaScript code. **Important**: Bundle **all** your JavaScript code into a single `script.js` file!
16-
- `routes`: Place for your (API) routes to connect your JS code with the Laravel Backend (`api.php`)
11+
```
12+
app/Plugins/YourPlugin/
13+
├── App
14+
│ ├── Controllers/ # Controller classes
15+
│ ├── Models/ # Laravel database models
16+
│ ├── Scopes/ # Model scopes
17+
│ ├── Support/ # Support classes
18+
│ ├── Services/ # Service classes that can be autoloaded into other classed
19+
│ ├── Utils/ # Static utility functions
20+
│ └── ...
21+
├── migrations/ # Plugin migrations
22+
│ ├── yyyy_mm_dd_hhmmss_migration_name.php # Migrations in this specific format.
23+
│ └── ...
24+
├── routes/ # Plugin routes
25+
├── src/ # Frontend source files
26+
├── js/
27+
│ ├── script.js.map # Compiled map file for development
28+
│ └── script.js # Compiled plugin script - REQUIRED
29+
├── vendor/ # Composer vendor directory generated by the plugin via composer install
30+
├── CHANGELOG.md # Version history
31+
├── composer.json # PHP dependencies
32+
├── package.json # JavaScript dependencies
33+
├── permissions.json # Plugin permissions
34+
├── plugin.xml # Plugin metadata - REQUIRED
35+
└── role-presets.json # Role presets
36+
```
1737

1838
## Creating a Plugin
1939

@@ -113,7 +133,7 @@ mkdir -p src/components
113133
mkdir src/i18n
114134
```
115135

116-
#### info.xml
136+
#### plugin.xml
117137

118138
This File is mandatory and stores all the information about your plugin. Author, Licence, Version, Description, ...
119139

@@ -128,6 +148,21 @@ This File is mandatory and stores all the information about your plugin. Author,
128148
<authors>
129149
<author>Vinzenz Rosenkranz</author>
130150
</authors>
151+
<dependencies>
152+
<depends-on plugin="file" min-version="2.0" />
153+
</dependencies>
154+
<routes>
155+
<route src="routes/api.php" />
156+
</routes>
157+
<hooks>
158+
<hook on="" />
159+
</hooks>
160+
<role-presets>
161+
<role-preset src="role-resets.json" />
162+
</role-presets>
163+
<permissions>
164+
<permission src="permission.json" />
165+
</permissions>
131166
</info>
132167

133168
```
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use App\Services\HookService;
6+
use Closure;
7+
use Illuminate\Http\Request;
8+
9+
class PluginHooks
10+
{
11+
12+
public function __construct(protected HookService $hookService){ }
13+
14+
15+
public function handle(Request $request, Closure $next) {
16+
$actionNamespace = $request->route()->getActionName();
17+
info("ActionName :: " . $actionNamespace);
18+
$namespaceParts = explode('\\', $actionNamespace);
19+
$name = array_pop($namespaceParts);
20+
21+
$response = $next($request);
22+
// execute hooks with the response as first argument so hooks can modify it
23+
$response = $this->hookService->executeHooks($name, $response, $request);
24+
25+
return $response;
26+
}
27+
}

app/Interfaces/IPluggable.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace App\Interfaces;
4+
5+
use App\Plugin;
6+
7+
interface IPluggable {
8+
public function install(Plugin $plugin): void;
9+
public function update(Plugin $plugin): void;
10+
public function uninstall(Plugin $plugin): void;
11+
public function remove(Plugin $plugin): void;
12+
}

app/Models/Plugin/Hook.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace App\Models\Plugin;
4+
5+
use App\Plugin;
6+
use Illuminate\Database\Eloquent\Model;
7+
8+
class Hook extends Model
9+
{
10+
public const AVAILABLE_HOOKS = [
11+
'App\Http\Controllers\HomeController@getGlobalData',
12+
'EditorController@setRelationInfo',
13+
'EditorController@getEntityType'
14+
];
15+
16+
protected $table = 'plugin_hooks';
17+
18+
protected $fillable = [
19+
'plugin_id',
20+
'src',
21+
'order',
22+
'on',
23+
];
24+
25+
/**
26+
* When a plugin xml is parsed, this method is used to create a Hook model from
27+
* the json representation of the hook in the plugin's info.xml.
28+
*
29+
* @throws \Exception if the json is missing required fields or has invalid values.
30+
* @return {Model(unsaved)} An unsaved Hook model instance.
31+
*/
32+
public static function getHookFromJson($hookJson, Plugin $plugin = null): Hook {
33+
$hook = $hookJson;
34+
35+
$missingFields = [];
36+
$requiredFields = ['on', 'src'];
37+
38+
foreach($requiredFields as $reqiredField){
39+
if(!isset($hook[$reqiredField])) {
40+
$missingFields[] = $reqiredField;
41+
}
42+
}
43+
44+
if(count($missingFields) > 0){
45+
info("MISSING FIELDS IN HOOK JSON: " . implode(", ", $missingFields) . " in hook json: " . json_encode($hookJson));
46+
throw new \Exception("Hook is missing field(s): " . implode(", ", $missingFields));
47+
}
48+
49+
if(!in_array($hook['on'], self::AVAILABLE_HOOKS)) {
50+
throw new \Exception("Hook '".$hook['on']."' is not a valid hook.");
51+
}
52+
53+
$src = $hook['src'];
54+
$parts = explode('@', $src);
55+
if(count($parts) != 2) {
56+
throw new \Exception("Hook 'src' field must be in the format 'class@method'");
57+
}
58+
59+
$order = isset($hook['order']) ? $hook['order'] : 0;
60+
$order = intval($order);
61+
62+
$hookModel = new Hook();
63+
$hookModel->on = $hook['on'];
64+
$hookModel->src = $hook['src'];
65+
$hookModel->order = $order;
66+
67+
if($plugin) {
68+
$hookModel->plugin_id = $plugin->id;
69+
}
70+
71+
return $hookModel;
72+
}
73+
}

app/Patterns/Singleton.php

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,26 @@
44

55
abstract class Singleton {
66

7-
public static ?Singleton $instance = null;
7+
// NOTE: The problem in PHP is that static properties are shared between child classes,
8+
// so we need to store the instance in an array with the class name as key.
9+
10+
/**
11+
* The instances of the singleton classes, stored with the class name as key.
12+
* This is necessary because static properties are shared between child classes in PHP.
13+
*
14+
* @var array<string, static> - An array that holds the instances of the singleton classes, indexed by their class names.
15+
*/
16+
protected static array $instances = [];
17+
18+
protected function __construct() {}
819

9-
protected function __construct() {
10-
static::$instance = $this;
11-
}
12-
13-
static function get(): static {
14-
if (static::$instance != null) {
15-
return static::$instance;
20+
/**
21+
* Returns the singleton instance of the class. If the instance does not exist, it creates a new one.
22+
*/
23+
final public static function get(): static {
24+
if(!isset(static::$instances[static::class])) {
25+
static::$instances[static::class] = new static();
1626
}
17-
return new static();
27+
return static::$instances[static::class];
1828
}
1929
}

0 commit comments

Comments
 (0)