PluginProbe
Noted! / trunk
Noted! vtrunk
2.0.2 2.0.1 trunk 1.0 2.0
noted / includes / Plugin.php

Plugin.php in Noted! trunk, at includes/Plugin.php

208 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Noted;
6
7 /**
8 * Plugin bootstrap and composition root.
9 *
10 * Owns one Settings instance and hands it to every service that needs to
11 * gate behaviour on user role or feature toggles. Side effects only run
12 * when {@see Plugin::init()} is invoked from the main plugin file.
13 */
14 final class Plugin
15 {
16 public const VERSION = '2.0.2';
17 public const OPTION_KEY = 'noted_settings';
18
19 private static ?self $instance = null;
20
21 private string $pluginFile;
22 private string $pluginDir;
23 private Settings $settings;
24
25 /**
26 * Get the singleton instance, instantiating it on first call.
27 */
28 public static function instance(): self
29 {
30 if (self::$instance === null) {
31 self::$instance = new self();
32 }
33 return self::$instance;
34 }
35
36 /**
37 * Private constructor — use {@see Plugin::instance()}.
38 */
39 private function __construct()
40 {
41 $this->pluginFile = NOTED_PLUGIN_FILE;
42 $this->pluginDir = NOTED_PLUGIN_DIR;
43 $this->settings = new Settings($this);
44 }
45
46 /**
47 * Register every WordPress hook for the plugin.
48 */
49 public function init(): void
50 {
51 Compatibility::register();
52
53 add_action('init', [$this, 'loadTextdomain']);
54
55 $this->settings->register();
56
57 (new PostType())->register();
58 (new RestApi($this->settings))->register();
59 (new AdminBar($this->settings))->register();
60 (new PageNotes($this->settings))->register();
61 (new DashboardWidget($this->settings))->register();
62 (new BlockNotes())->register();
63 (new Assets($this, $this->settings))->register();
64 }
65
66 /**
67 * Load the plugin text domain so PHP gettext calls resolve. JS
68 * translations are wired separately via wp_set_script_translations().
69 */
70 public function loadTextdomain(): void
71 {
72 load_plugin_textdomain(
73 'noted',
74 false,
75 dirname(plugin_basename($this->pluginFile)) . '/languages'
76 );
77 }
78
79 /**
80 * Absolute path to the plugin's languages directory.
81 */
82 public function languagesDir(): string
83 {
84 return $this->pluginDir . '/languages';
85 }
86
87 /**
88 * Access the shared Settings instance.
89 */
90 public function settings(): Settings
91 {
92 return $this->settings;
93 }
94
95 /**
96 * Absolute path to the main plugin file (used by plugins_url()).
97 */
98 public function pluginFile(): string
99 {
100 return $this->pluginFile;
101 }
102
103 /**
104 * Absolute path to the plugin directory.
105 */
106 public function pluginDir(): string
107 {
108 return $this->pluginDir;
109 }
110
111 /**
112 * Build a URL for an asset inside the assets/ directory.
113 *
114 * @param string $relative Path inside assets/ (e.g. "js/api.js").
115 */
116 public function assetUrl(string $relative): string
117 {
118 return plugins_url('assets/' . ltrim($relative, '/'), $this->pluginFile);
119 }
120
121 /**
122 * SVG markup for the plugin icon.
123 *
124 * Filterable via `noted/icon_svg` — return any inline SVG and every
125 * surface that uses the icon (admin bar, block-editor sidebar) picks
126 * up the change. The output is sanitised against an SVG allowlist so
127 * a third-party filter cannot inject scripts.
128 */
129 public function iconSvg(): string
130 {
131 $default = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill-rule="evenodd" clip-rule="evenodd" d="M6.68822 16.625L5.5 17.8145L5.5 5.5L18.5 5.5L18.5 16.625L6.68822 16.625ZM7.31 18.125L19 18.125C19.5523 18.125 20 17.6773 20 17.125L20 5C20 4.44772 19.5523 4 19 4H5C4.44772 4 4 4.44772 4 5V19.5247C4 19.8173 4.16123 20.086 4.41935 20.2237C4.72711 20.3878 5.10601 20.3313 5.35252 20.0845L7.31 18.125ZM16 9.99997H8V8.49997H16V9.99997ZM8 14H13V12.5H8V14Z"/></svg>';
132 $filtered = (string) apply_filters('noted/icon_svg', $default);
133 return wp_kses($filtered, $this->allowedIconSvgTags());
134 }
135
136 /**
137 * Allowed SVG tag/attribute map for sanitising icon markup.
138 *
139 * @return array<string, array<string, bool>>
140 */
141 private function allowedIconSvgTags(): array
142 {
143 return [
144 'svg' => [
145 'xmlns' => true,
146 'viewbox' => true,
147 'fill' => true,
148 'width' => true,
149 'height' => true,
150 'aria-hidden' => true,
151 'focusable' => true,
152 'role' => true,
153 ],
154 'path' => [
155 'd' => true,
156 'fill' => true,
157 'fill-rule' => true,
158 'clip-rule' => true,
159 'stroke' => true,
160 'stroke-width' => true,
161 'stroke-linecap' => true,
162 'stroke-linejoin'=> true,
163 ],
164 'g' => [
165 'fill' => true,
166 'transform' => true,
167 ],
168 'circle' => [
169 'cx' => true,
170 'cy' => true,
171 'r' => true,
172 'fill' => true,
173 'stroke' => true,
174 'stroke-width' => true,
175 ],
176 'rect' => [
177 'x' => true,
178 'y' => true,
179 'width' => true,
180 'height' => true,
181 'rx' => true,
182 'ry' => true,
183 'fill' => true,
184 ],
185 'line' => [
186 'x1' => true,
187 'y1' => true,
188 'x2' => true,
189 'y2' => true,
190 'stroke' => true,
191 'stroke-width' => true,
192 ],
193 'polygon' => [
194 'points' => true,
195 'fill' => true,
196 ],
197 'polyline' => [
198 'points' => true,
199 'stroke' => true,
200 'stroke-width' => true,
201 'fill' => true,
202 ],
203 'title' => [],
204 'desc' => [],
205 ];
206 }
207 }
208