PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.11
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.11
1.1.11 1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 All 35 releases
desktop-mode / includes / framework / app / class-os.php

class-os.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.11, at includes/framework/app/class-os.php

459 lines 12.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation App Framework — the `$os` handle.
4 *
5 * Every callback an app writes — the gate, `mount`, each action, the
6 * view — receives one `Os`. It is the data-access layer: the six
7 * host contracts (auth, settings, hooks, cache, env, store) behind
8 * one object, plus what the current dispatch brought along (the
9 * client viewport, the window's open-time params) and the effects
10 * queue it will take back. An app that only talks to `$os` runs on
11 * WordPress and on a bare PHP host without a line changed.
12 *
13 * @package OpenStation
14 */
15
16 namespace OpenStation\App;
17
18 use OpenStation\App\Contracts\Auth;
19 use OpenStation\App\Contracts\Cache;
20 use OpenStation\App\Contracts\Env;
21 use OpenStation\App\Contracts\Hooks;
22 use OpenStation\App\Contracts\Settings;
23 use OpenStation\App\Contracts\Store;
24
25 // Direct access, unless a standalone host is booting on bare PHP.
26 if ( ! defined( 'ABSPATH' ) ) {
27 defined( 'OPENSTATION_STANDALONE' ) || exit;
28 }
29
30 /**
31 * The host handle every app callback receives.
32 */
33 final class Os {
34
35 /**
36 * @var Auth
37 */
38 public $auth;
39
40 /**
41 * @var Settings
42 */
43 public $settings;
44
45 /**
46 * @var Hooks
47 */
48 public $hooks;
49
50 /**
51 * @var Cache
52 */
53 public $cache;
54
55 /**
56 * @var Env
57 */
58 public $env;
59
60 /**
61 * @var Store
62 */
63 public $storage;
64
65 /**
66 * Effects queued during the current dispatch.
67 *
68 * @var Effects
69 */
70 public $effects;
71
72 /**
73 * Client viewport as reported with the dispatch (`width`, `height`).
74 *
75 * @var array{width:int,height:int}
76 */
77 public $client = array(
78 'width' => 0,
79 'height' => 0,
80 );
81
82 /**
83 * The window's open-time params (`wp.os.openWindow( id, { params } )`).
84 *
85 * @var array<string,scalar>
86 */
87 public $params = array();
88
89 /**
90 * Id of the app being dispatched; '' outside a dispatch.
91 *
92 * @var string
93 */
94 public $app_id = '';
95
96 /**
97 * The view being rendered: `main` or a tab value.
98 *
99 * @var string
100 */
101 public $view = 'main';
102
103 public function __construct( Auth $auth, Settings $settings, Hooks $hooks, Cache $cache, Env $env, ?Store $storage = null ) {
104 $this->auth = $auth;
105 $this->settings = $settings;
106 $this->hooks = $hooks;
107 $this->cache = $cache;
108 $this->env = $env;
109 $this->storage = $storage ? $storage : new Standalone\Store();
110 $this->effects = new Effects();
111 }
112
113 /**
114 * A host built from the standalone adapters — what tests and
115 * plain PHP hosts use. Pass any contract to override just that one.
116 *
117 * @param array<string,object> $overrides `auth` | `settings` | `hooks` | `cache` | `env` | `store`.
118 * @return self
119 */
120 public static function standalone( array $overrides = array() ) {
121 return new self(
122 isset( $overrides['auth'] ) ? $overrides['auth'] : new Standalone\Auth( 1, array( '*' ) ),
123 isset( $overrides['settings'] ) ? $overrides['settings'] : new Standalone\Settings(),
124 isset( $overrides['hooks'] ) ? $overrides['hooks'] : new Standalone\Hooks(),
125 isset( $overrides['cache'] ) ? $overrides['cache'] : new Standalone\Cache(),
126 isset( $overrides['env'] ) ? $overrides['env'] : new Standalone\Env(),
127 isset( $overrides['store'] ) ? $overrides['store'] : new Standalone\Store()
128 );
129 }
130
131 /**
132 * Start a fresh dispatch: new effects queue, new client facts.
133 *
134 * @param array<string,mixed> $client `width` / `height` from the client.
135 * @param array<string,mixed> $params The window's open-time params.
136 * @param string $app_id App being dispatched.
137 * @param string $view View being rendered.
138 * @return self
139 */
140 public function begin( array $client = array(), array $params = array(), $app_id = '', $view = 'main' ) {
141 $this->effects = new Effects();
142 $this->client = array(
143 'width' => isset( $client['width'] ) ? max( 0, (int) $client['width'] ) : 0,
144 'height' => isset( $client['height'] ) ? max( 0, (int) $client['height'] ) : 0,
145 );
146 $this->params = array_filter( $params, 'is_scalar' );
147 $this->app_id = (string) $app_id;
148 $this->view = '' !== (string) $view ? (string) $view : 'main';
149 return $this;
150 }
151
152 // ------------------------------------------------------------ sugar
153
154 /**
155 * Whether the acting user holds a capability. Extra arguments
156 * address a meta-capability's object: `can( 'delete_post', $id )`.
157 *
158 * @param string $capability Capability slug.
159 * @param mixed ...$args Object the capability is asked against.
160 * @return bool
161 */
162 public function can( $capability, ...$args ) {
163 return $this->auth->can( $capability, ...$args );
164 }
165
166 /**
167 * A preference of the acting user.
168 *
169 * @param string $key Preference key.
170 * @param mixed $fallback Fallback.
171 * @return mixed
172 */
173 public function preference( $key, $fallback = null ) {
174 return $this->settings->user_preference( $key, $fallback );
175 }
176
177 /**
178 * One of the window's open-time params.
179 *
180 * @param string $key Param name.
181 * @param mixed $fallback Fallback.
182 * @return mixed
183 */
184 public function param( $key, $fallback = null ) {
185 return array_key_exists( $key, $this->params ) ? $this->params[ $key ] : $fallback;
186 }
187
188 /**
189 * The paged-list envelope — the one shape the client runtime's
190 * page accumulation understands, so every list-shaped `data()`
191 * key builds it here instead of hand-assembling the array (five
192 * hand-assembled copies is how the first app shipped).
193 *
194 * @param array<int,mixed> $items This page's rows.
195 * @param int $total Total rows across all pages.
196 * @param int $page 1-based page number.
197 * @param int $per_page Rows per page.
198 * @return array{items:array<int,mixed>,total:int,pages:int,page:int,perPage:int}
199 */
200 public static function page( array $items, $total, $page, $per_page ) {
201 $total = max( 0, (int) $total );
202 $per = max( 1, (int) $per_page );
203 return array(
204 'items' => array_values( $items ),
205 'total' => $total,
206 'pages' => max( 1, (int) ceil( $total / $per ) ),
207 'page' => max( 1, (int) $page ),
208 'perPage' => $per,
209 );
210 }
211
212 /**
213 * Keep only the facts that have a value.
214 *
215 * A detail pane is a list of `array( label, value )` rows (an
216 * optional third element tags the row for filters), and a row
217 * whose value came back empty should vanish rather than render a
218 * labelled blank. One definition of "empty" for every pane.
219 *
220 * @param array<int,array<int,string>> $rows Label/value(/tag) rows.
221 * @return array<int,array<int,string>>
222 */
223 public static function facts( array $rows ) {
224 return array_values(
225 array_filter(
226 $rows,
227 static function ( $fact ) {
228 return isset( $fact[1] ) && '' !== (string) $fact[1];
229 }
230 )
231 );
232 }
233
234 /**
235 * Run a value through a filter hook.
236 *
237 * @param string $hook Hook name.
238 * @param mixed $value Value.
239 * @param mixed ...$args Extra callback arguments.
240 * @return mixed
241 */
242 public function filter( $hook, $value, ...$args ) {
243 return $this->hooks->filter( $hook, $value, ...$args );
244 }
245
246 /**
247 * Fire an action hook.
248 *
249 * @param string $hook Hook name.
250 * @param mixed ...$args Callback arguments.
251 * @return void
252 */
253 public function action( $hook, ...$args ) {
254 $this->hooks->action( $hook, ...$args );
255 }
256
257 /**
258 * Return a cached value, computing and storing it on a miss.
259 *
260 * The key is the whole contract, and on a persistent object cache
261 * it is shared by every request on the site. Two things belong in
262 * it that are easy to forget: anything a **filter** contributed to
263 * the value (run the filter outside `$compute` and fold its result
264 * into the key, or a plugin's change lags by the TTL) and the
265 * **locale**, whenever the value carries translated text (or an
266 * admin reading in one language is served another's labels).
267 *
268 * @param string $key Cache key.
269 * @param int $ttl Seconds to keep it.
270 * @param callable $compute Produces the value on a miss.
271 * @return mixed
272 */
273 public function remember( $key, $ttl, callable $compute ) {
274 $miss = new \stdClass();
275 $value = $this->cache->get( $key, $miss );
276 if ( $miss === $value ) {
277 $value = $compute();
278 $this->cache->set( $key, $value, $ttl );
279 }
280 return $value;
281 }
282
283 // ---------------------------------------------------------- storage
284
285 /**
286 * Read a value this app stored (keys are namespaced per app).
287 *
288 * @param string $key Key.
289 * @param mixed $fallback Fallback.
290 * @param string $scope `user` (default) | `site`.
291 * @return mixed
292 */
293 public function stored( $key, $fallback = null, $scope = 'user' ) {
294 return $this->storage->get( $scope, $this->storage_key( $key ), $fallback );
295 }
296
297 /**
298 * Store a value for this app.
299 *
300 * @param string $key Key.
301 * @param mixed $value Serialisable value.
302 * @param string $scope `user` (default) | `site`.
303 * @return self
304 */
305 public function store( $key, $value, $scope = 'user' ) {
306 $this->storage->set( $scope, $this->storage_key( $key ), $value );
307 return $this;
308 }
309
310 /**
311 * Remove a stored value.
312 *
313 * @param string $key Key.
314 * @param string $scope `user` (default) | `site`.
315 * @return self
316 */
317 public function forget( $key, $scope = 'user' ) {
318 $this->storage->delete( $scope, $this->storage_key( $key ) );
319 return $this;
320 }
321
322 /**
323 * Namespace a storage key by the current app.
324 *
325 * @param string $key Key.
326 * @return string
327 */
328 private function storage_key( $key ) {
329 return ( '' !== $this->app_id ? $this->app_id . ':' : '' ) . (string) $key;
330 }
331
332 // ---------------------------------------------------------- effects
333
334 /**
335 * Queue a toast. See {@see Effects::toast()}.
336 *
337 * @param string $message Text.
338 * @param string $type Toast-type id (`error`, `success`, …), or '' for the plain toast.
339 * @return self
340 */
341 public function toast( $message, $type = '' ) {
342 $this->effects->toast( $message, $type );
343 return $this;
344 }
345
346 /**
347 * Queue a retitle. See {@see Effects::title()}.
348 *
349 * @param string $title New title.
350 * @return self
351 */
352 public function title( $title ) {
353 $this->effects->title( $title );
354 return $this;
355 }
356
357 /**
358 * Queue a close. See {@see Effects::close()}.
359 *
360 * @return self
361 */
362 public function close() {
363 $this->effects->close();
364 return $this;
365 }
366
367 /**
368 * Queue an open. See {@see Effects::open()}.
369 *
370 * @param string $window_id Native window id.
371 * @return self
372 */
373 public function open( $window_id ) {
374 $this->effects->open( $window_id );
375 return $this;
376 }
377
378 /**
379 * Queue an admin-URL window open. See {@see Effects::open_url()}.
380 *
381 * @param string $url Admin URL.
382 * @param string $title Title.
383 * @param string $icon Icon (Dashicons class or image URL).
384 * @return self
385 */
386 public function open_url( $url, $title = '', $icon = '' ) {
387 $this->effects->open_url( $url, $title, $icon );
388 return $this;
389 }
390
391 /**
392 * Queue a badge update. See {@see Effects::badge()}.
393 *
394 * @param int $count Count; 0 clears.
395 * @return self
396 */
397 public function badge( $count ) {
398 $this->effects->badge( $count );
399 return $this;
400 }
401
402 /**
403 * Queue a tile-art swap. See {@see Effects::icon()}.
404 *
405 * @param string $icon SVG data URI or image URL.
406 * @return self
407 */
408 public function icon( $icon ) {
409 $this->effects->icon( $icon );
410 return $this;
411 }
412
413 /**
414 * Queue a content-change announcement. See {@see Effects::announce()}.
415 *
416 * @param string $type Content type.
417 * @param string $action Change kind.
418 * @param int|int[] $ids Affected ids.
419 * @return self
420 */
421 public function announce( $type, $action, $ids ) {
422 $this->effects->announce( $type, $action, $ids );
423 return $this;
424 }
425
426 /**
427 * Queue a context menu. See {@see Effects::menu()}.
428 *
429 * @param array<int,array<string,mixed>> $items Menu items.
430 * @return self
431 */
432 public function menu( array $items ) {
433 $this->effects->menu( $items );
434 return $this;
435 }
436
437 /**
438 * Queue a channel publish. See {@see Effects::send()}.
439 *
440 * @param string $channel Channel.
441 * @param mixed $payload Payload.
442 * @return self
443 */
444 public function send( $channel, $payload = null ) {
445 $this->effects->send( $channel, $payload );
446 return $this;
447 }
448
449 /**
450 * Queue a menu-payload refresh. See {@see Effects::refresh_menu()}.
451 *
452 * @return self
453 */
454 public function refresh_menu() {
455 $this->effects->refresh_menu();
456 return $this;
457 }
458 }
459