PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.9
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.9
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 0.8.7 0.8.6 All 33 releases
desktop-mode / includes / framework / app / class-os.php

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

458 lines 11.9 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 * @return self
339 */
340 public function toast( $message ) {
341 $this->effects->toast( $message );
342 return $this;
343 }
344
345 /**
346 * Queue a retitle. See {@see Effects::title()}.
347 *
348 * @param string $title New title.
349 * @return self
350 */
351 public function title( $title ) {
352 $this->effects->title( $title );
353 return $this;
354 }
355
356 /**
357 * Queue a close. See {@see Effects::close()}.
358 *
359 * @return self
360 */
361 public function close() {
362 $this->effects->close();
363 return $this;
364 }
365
366 /**
367 * Queue an open. See {@see Effects::open()}.
368 *
369 * @param string $window_id Native window id.
370 * @return self
371 */
372 public function open( $window_id ) {
373 $this->effects->open( $window_id );
374 return $this;
375 }
376
377 /**
378 * Queue an admin-URL window open. See {@see Effects::open_url()}.
379 *
380 * @param string $url Admin URL.
381 * @param string $title Title.
382 * @param string $icon Icon (Dashicons class or image URL).
383 * @return self
384 */
385 public function open_url( $url, $title = '', $icon = '' ) {
386 $this->effects->open_url( $url, $title, $icon );
387 return $this;
388 }
389
390 /**
391 * Queue a badge update. See {@see Effects::badge()}.
392 *
393 * @param int $count Count; 0 clears.
394 * @return self
395 */
396 public function badge( $count ) {
397 $this->effects->badge( $count );
398 return $this;
399 }
400
401 /**
402 * Queue a tile-art swap. See {@see Effects::icon()}.
403 *
404 * @param string $icon SVG data URI or image URL.
405 * @return self
406 */
407 public function icon( $icon ) {
408 $this->effects->icon( $icon );
409 return $this;
410 }
411
412 /**
413 * Queue a content-change announcement. See {@see Effects::announce()}.
414 *
415 * @param string $type Content type.
416 * @param string $action Change kind.
417 * @param int|int[] $ids Affected ids.
418 * @return self
419 */
420 public function announce( $type, $action, $ids ) {
421 $this->effects->announce( $type, $action, $ids );
422 return $this;
423 }
424
425 /**
426 * Queue a context menu. See {@see Effects::menu()}.
427 *
428 * @param array<int,array<string,mixed>> $items Menu items.
429 * @return self
430 */
431 public function menu( array $items ) {
432 $this->effects->menu( $items );
433 return $this;
434 }
435
436 /**
437 * Queue a channel publish. See {@see Effects::send()}.
438 *
439 * @param string $channel Channel.
440 * @param mixed $payload Payload.
441 * @return self
442 */
443 public function send( $channel, $payload = null ) {
444 $this->effects->send( $channel, $payload );
445 return $this;
446 }
447
448 /**
449 * Queue a menu-payload refresh. See {@see Effects::refresh_menu()}.
450 *
451 * @return self
452 */
453 public function refresh_menu() {
454 $this->effects->refresh_menu();
455 return $this;
456 }
457 }
458