PluginProbe
Site Reviews / trunk
Site Reviews vtrunk
8.3.1 8.3.0 8.2.2 8.2.1 8.2.0 8.1.0 8.0.13 8.0.12 8.0.11 trunk 1.2.2 2.17.1 3.5.4 4.7.0 5.25.1 6.11.8 7.0.10 7.0.11 7.0.12 7.0.13 7.0.14 7.0.15 7.0.16 7.0.17 7.0.18 All 54 releases
site-reviews / plugin / Plugin.php

Plugin.php in Site Reviews trunk, at plugin/Plugin.php

348 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace GeminiLabs\SiteReviews;
4
5 use GeminiLabs\SiteReviews\Helpers\Arr;
6 use GeminiLabs\SiteReviews\Helpers\Cast;
7 use GeminiLabs\SiteReviews\Helpers\Str;
8
9 /**
10 * @property string $id
11 * @property string $name
12 * @property string $post_type
13 *
14 * @method array filterArray($hook, ...$args)
15 * @method bool filterBool($hook, ...$args)
16 * @method float filterFloat($hook, ...$args)
17 * @method int filterInt($hook, ...$args)
18 * @method object filterObject($hook, ...$args)
19 * @method string filterString($hook, ...$args)
20 */
21 trait Plugin
22 {
23 /**
24 * @var static|null
25 */
26 protected static $instance;
27
28 protected $basename;
29 protected $file;
30 protected $languages;
31 protected $testedTo;
32 protected $updateUrl;
33 protected $uri;
34 protected $version;
35
36 public function __call($method, $args)
37 {
38 $isFilter = str_starts_with($method, 'filter');
39 $cast = Str::removePrefix($method, 'filter');
40 $to = Helper::buildMethodName('to', $cast);
41 if ($isFilter && method_exists(Cast::class, $to)) {
42 $filtered = call_user_func_array([$this, 'filter'], $args);
43 return Cast::$to($filtered);
44 }
45 throw new \BadMethodCallException("Method [$method] does not exist.");
46 }
47
48 public function __construct()
49 {
50 $file = wp_normalize_path((new \ReflectionClass($this))->getFileName());
51 $this->file = str_replace('plugin/Application', $this->id, $file);
52 $this->basename = plugin_basename($this->file);
53 $plugin = get_file_data($this->file, [
54 'languages' => 'Domain Path',
55 'name' => 'Plugin Name',
56 'testedTo' => 'Tested up to',
57 'uri' => 'Plugin URI',
58 'updateUrl' => 'Update URI',
59 'version' => 'Version',
60 ], 'plugin');
61 array_walk($plugin, function ($value, $key) {
62 if (property_exists($this, $key)) {
63 $this->$key = $value;
64 }
65 });
66 }
67
68 public function __get($property)
69 {
70 $instance = new \ReflectionClass($this);
71 if ($instance->hasProperty($property)) {
72 $prop = $instance->getProperty($property);
73 if ($prop->isPublic() || $prop->isProtected()) {
74 return $this->$property;
75 }
76 }
77 $constant = strtoupper($property);
78 if ($instance->hasConstant($constant)) {
79 return $instance->getConstant($constant);
80 }
81 }
82
83 /**
84 * isset()/empty() on an inaccessible property consult THIS, never __get() — without it
85 * they answer false/true regardless of what the property holds, and a guard like
86 * !empty(glsr()->settings) can never pass.
87 */
88 public function __isset($property)
89 {
90 $instance = new \ReflectionClass($this);
91 if ($instance->hasProperty($property)) {
92 $prop = $instance->getProperty($property);
93 return ($prop->isPublic() || $prop->isProtected()) && $prop->isInitialized($this);
94 }
95 return $instance->hasConstant(strtoupper($property));
96 }
97
98 /**
99 * @param mixed ...$args
100 */
101 public function action(string $hook, ...$args): void
102 {
103 $prefix = $this->hookPrefix();
104 do_action("{$prefix}/action", $hook, $args);
105 do_action_ref_array("{$prefix}/{$hook}", $args);
106 }
107
108 /**
109 * @param mixed $args
110 */
111 public function args($args = []): Arguments
112 {
113 return new Arguments($args);
114 }
115
116 public function build(string $view, array $data = []): string
117 {
118 ob_start();
119 $this->render($view, $data);
120 return trim(ob_get_clean());
121 }
122
123 public function catchFatalError(): void
124 {
125 $error = error_get_last();
126 if (\E_ERROR === Arr::get($error, 'type') && str_contains(Arr::get($error, 'message'), $this->path())) {
127 glsr_log()->error($error['message']);
128 }
129 }
130
131 public function config(string $name, bool $filtered = true): array
132 {
133 $path = $this->filterString('config', "config/{$name}.php");
134 $configFile = $this->path($path);
135 $config = file_exists($configFile)
136 ? include $configFile
137 : [];
138 $config = Arr::consolidate($config);
139 // Don't filter the settings config!
140 // They can be filtered with the "site-reviews/settings" filter hook
141 if ($filtered && !Str::contains($name, ['integrations', 'settings'])) {
142 $config = $this->filterArray("config/{$name}", $config);
143 }
144 return $config;
145 }
146
147 /**
148 * @return mixed
149 */
150 public function constant(string $property, string $className = 'static')
151 {
152 $property = strtoupper($property);
153 $constant = "{$className}::{$property}";
154 return defined($constant)
155 ? $this->filterString("const/{$property}", constant($constant))
156 : '';
157 }
158
159 public function file(string $view): string
160 {
161 $view .= '.php';
162 $filePaths = [];
163 if (str_starts_with($view, 'templates/')) {
164 $filePaths[] = $this->themePath(Str::removePrefix($view, 'templates/'));
165 }
166 $filePaths[] = $this->path($view);
167 $filePaths[] = $this->path("views/{$view}");
168 foreach ($filePaths as $file) {
169 if (file_exists($file)) {
170 return $file;
171 }
172 }
173 return '';
174 }
175
176 /**
177 * @param mixed ...$args
178 *
179 * @return mixed
180 */
181 public function filter(string $hook, ...$args)
182 {
183 $prefix = $this->hookPrefix();
184 do_action("{$prefix}/filter", $hook, $args);
185 return apply_filters_ref_array("{$prefix}/{$hook}", $args);
186 }
187
188 /**
189 * @param mixed ...$args
190 */
191 public function filterArrayUnique(string $hook, ...$args): array
192 {
193 $prefix = $this->hookPrefix();
194 do_action("{$prefix}/filter", $hook, $args);
195 $filtered = apply_filters_ref_array("{$prefix}/{$hook}", $args);
196 return array_unique(array_filter(Cast::toArray($filtered)));
197 }
198
199 /**
200 * @param mixed ...$args
201 */
202 public function filterArrayUniqueInt(string $hook, ...$args): array
203 {
204 $prefix = $this->hookPrefix();
205 do_action("{$prefix}/filter", $hook, $args);
206 $filtered = apply_filters_ref_array("{$prefix}/{$hook}", $args);
207 return Arr::uniqueInt(Cast::toArray($filtered));
208 }
209
210 /**
211 * @param mixed ...$args
212 */
213 public function filterArrayUniqueString(string $hook, ...$args): array
214 {
215 $prefix = $this->hookPrefix();
216 do_action("{$prefix}/filter", $hook, $args);
217 $filtered = apply_filters_ref_array("{$prefix}/{$hook}", $args);
218 return Arr::uniqueString(Cast::toArray($filtered));
219 }
220
221 /**
222 * Whether this plugin registers its own post type. The parent plugin always
223 * does; only around half of the addons do.
224 */
225 public function hasPostType(): bool
226 {
227 return !empty($this->post_type); // the raw constant, unfiltered
228 }
229
230 /**
231 * What every hook this plugin fires is namespaced with. The plugin id,
232 * unless the plugin runs inside another one (see Addons\Addon).
233 */
234 public function hookPrefix(): string
235 {
236 return $this->id;
237 }
238
239 /**
240 * @return static
241 */
242 public static function load()
243 {
244 if (empty(static::$instance)) {
245 static::$instance = new static();
246 }
247 return static::$instance;
248 }
249
250 /**
251 * @param mixed $fallback
252 *
253 * @return mixed
254 */
255 public function option(string $path = '', $fallback = '', string $cast = '')
256 {
257 return glsr_get_option($path, $fallback, $cast);
258 }
259
260 /**
261 * Resolves a settings path against this plugin's mount in the composed
262 * view. For the raw arrays that never reach OptionManager::get()/set():
263 * a site-reviews/settings/sanitize callback is handed the composed tree
264 * and the submitted form, and both are keyed by the mount.
265 */
266 public function settingPath(string $path = ''): string
267 {
268 $path = Str::removePrefix(trim($path), 'settings.');
269 return implode('.', array_filter(['settings', $this->settingsPath(), trim($path, '.')]));
270 }
271
272 /**
273 * The plugin's mount point inside the composed settings view (without the
274 * leading "settings."). The core plugin owns the root; addons override
275 * this with their own mount (see Addons\Addon::settingsPath()).
276 */
277 public function settingsPath(): string
278 {
279 return '';
280 }
281
282 public function path(string $file = '', bool $realpath = true): string
283 {
284 $basedir = plugin_dir_path($this->file);
285 if (!$realpath) {
286 $basedir = trailingslashit(\WP_PLUGIN_DIR).basename(dirname($this->file));
287 }
288 $basedir = trailingslashit($basedir);
289 if (str_starts_with($file, $basedir)) {
290 $file = substr($file, strlen($basedir));
291 }
292 $path = $basedir.ltrim(trim($file), '/');
293 return $this->filterString('path', $path, $file);
294 }
295
296 public function render(string $view, array $data = []): void
297 {
298 $view = $this->filterString('render/view', $view, $data);
299 $file = $this->filterString('views/file', $this->file($view), $view, $data);
300 if (!file_exists($file)) {
301 glsr_log()->error(sprintf('File not found: (%s) %s', $view, $file));
302 return;
303 }
304 $data = $this->filterArray('views/data', $data, $view, $file);
305 $data = $this->filterArray("views/data/{$view}", $data, $file);
306 extract($data);
307 include $file;
308 }
309
310 /**
311 * @param mixed $args
312 */
313 public function request($args = []): Request
314 {
315 return new Request($args);
316 }
317
318 /**
319 * @return mixed|false
320 */
321 public function runIf(string $className, ...$args)
322 {
323 return class_exists($className)
324 ? call_user_func_array([glsr($className), 'handle'], $args)
325 : false;
326 }
327
328 public function themePath(string $file = ''): string
329 {
330 return get_stylesheet_directory().'/'.$this->id.'/'.ltrim(trim($file), '/');
331 }
332
333 public function url(string $path = ''): string
334 {
335 $basedir = plugin_dir_path($this->file);
336 if (str_starts_with($path, $basedir)) {
337 $path = substr($path, strlen($basedir));
338 }
339 $url = esc_url(plugin_dir_url($this->file).ltrim(trim($path), '/'));
340 return $this->filterString('url', $url, $path);
341 }
342
343 public function version(string $versionLevel = ''): string
344 {
345 return Helper::version($this->version, $versionLevel);
346 }
347 }
348