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

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

207 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.0';
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 Assets($this, $this->settings))->register();
63 }
64
65 /**
66 * Load the plugin text domain so PHP gettext calls resolve. JS
67 * translations are wired separately via wp_set_script_translations().
68 */
69 public function loadTextdomain(): void
70 {
71 load_plugin_textdomain(
72 'noted',
73 false,
74 dirname(plugin_basename($this->pluginFile)) . '/languages'
75 );
76 }
77
78 /**
79 * Absolute path to the plugin's languages directory.
80 */
81 public function languagesDir(): string
82 {
83 return $this->pluginDir . '/languages';
84 }
85
86 /**
87 * Access the shared Settings instance.
88 */
89 public function settings(): Settings
90 {
91 return $this->settings;
92 }
93
94 /**
95 * Absolute path to the main plugin file (used by plugins_url()).
96 */
97 public function pluginFile(): string
98 {
99 return $this->pluginFile;
100 }
101
102 /**
103 * Absolute path to the plugin directory.
104 */
105 public function pluginDir(): string
106 {
107 return $this->pluginDir;
108 }
109
110 /**
111 * Build a URL for an asset inside the assets/ directory.
112 *
113 * @param string $relative Path inside assets/ (e.g. "js/api.js").
114 */
115 public function assetUrl(string $relative): string
116 {
117 return plugins_url('assets/' . ltrim($relative, '/'), $this->pluginFile);
118 }
119
120 /**
121 * SVG markup for the plugin icon.
122 *
123 * Filterable via `noted/icon_svg` — return any inline SVG and every
124 * surface that uses the icon (admin bar, block-editor sidebar) picks
125 * up the change. The output is sanitised against an SVG allowlist so
126 * a third-party filter cannot inject scripts.
127 */
128 public function iconSvg(): string
129 {
130 $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>';
131 $filtered = (string) apply_filters('noted/icon_svg', $default);
132 return wp_kses($filtered, $this->allowedIconSvgTags());
133 }
134
135 /**
136 * Allowed SVG tag/attribute map for sanitising icon markup.
137 *
138 * @return array<string, array<string, bool>>
139 */
140 private function allowedIconSvgTags(): array
141 {
142 return [
143 'svg' => [
144 'xmlns' => true,
145 'viewbox' => true,
146 'fill' => true,
147 'width' => true,
148 'height' => true,
149 'aria-hidden' => true,
150 'focusable' => true,
151 'role' => true,
152 ],
153 'path' => [
154 'd' => true,
155 'fill' => true,
156 'fill-rule' => true,
157 'clip-rule' => true,
158 'stroke' => true,
159 'stroke-width' => true,
160 'stroke-linecap' => true,
161 'stroke-linejoin'=> true,
162 ],
163 'g' => [
164 'fill' => true,
165 'transform' => true,
166 ],
167 'circle' => [
168 'cx' => true,
169 'cy' => true,
170 'r' => true,
171 'fill' => true,
172 'stroke' => true,
173 'stroke-width' => true,
174 ],
175 'rect' => [
176 'x' => true,
177 'y' => true,
178 'width' => true,
179 'height' => true,
180 'rx' => true,
181 'ry' => true,
182 'fill' => true,
183 ],
184 'line' => [
185 'x1' => true,
186 'y1' => true,
187 'x2' => true,
188 'y2' => true,
189 'stroke' => true,
190 'stroke-width' => true,
191 ],
192 'polygon' => [
193 'points' => true,
194 'fill' => true,
195 ],
196 'polyline' => [
197 'points' => true,
198 'stroke' => true,
199 'stroke-width' => true,
200 'fill' => true,
201 ],
202 'title' => [],
203 'desc' => [],
204 ];
205 }
206 }
207