| 1 |
<?php |
| 2 |
/** |
| 3 |
* ============================================================================= |
| 4 |
* OpenStation — Starter Widget PHP registration |
| 5 |
* ============================================================================= |
| 6 |
* |
| 7 |
* WHAT THIS FILE DOES |
| 8 |
* ------------------- |
| 9 |
* This is the PHP side of a OpenStation widget. It has three jobs: |
| 10 |
* |
| 11 |
* 1. Register the JS bundle and CSS with WordPress so they can be loaded. |
| 12 |
* 2. Enqueue the CSS eagerly on shell pages (prevents flash of unstyled content). |
| 13 |
* 3. Announce the widget to OpenStation via openstation_register_widget() |
| 14 |
* so it appears in the widget picker. |
| 15 |
* |
| 16 |
* The PHP side does NOT contain any widget logic. All the rendering, data |
| 17 |
* fetching, and interactivity lives in the JS file (index.ts). PHP just |
| 18 |
* tells the system "this widget exists, here is its metadata, here is its |
| 19 |
* script handle." |
| 20 |
* |
| 21 |
* HOW TO USE THIS AS A TEMPLATE |
| 22 |
* ------------------------------ |
| 23 |
* 1. Copy this file to includes/widgets/widget-my.php |
| 24 |
* 2. Replace every "starter" in function names with your widget name |
| 25 |
* e.g. openstation_register_starter_* → openstation_register_my_* |
| 26 |
* 3. Replace "widget-starter" in asset filenames with "widget-my" |
| 27 |
* 4. Replace 'desktop-mode/starter' with your widget id — must match |
| 28 |
* the WIDGET_ID constant in your JS file exactly |
| 29 |
* 5. Update label, description, icon, and size constraints |
| 30 |
* 6. Add a require_once line for this file in desktop-mode.php |
| 31 |
* |
| 32 |
* Requires: OpenStation 0.18.0+ (openstation_register_widget). |
| 33 |
* |
| 34 |
* @package OpenStation |
| 35 |
*/ |
| 36 |
|
| 37 |
defined( 'ABSPATH' ) || exit; |
| 38 |
|
| 39 |
/** |
| 40 |
* Register the JS bundle and CSS stylesheet handles. |
| 41 |
* |
| 42 |
* WHY wp_register_script AND NOT wp_enqueue_script? |
| 43 |
* The shell's server-sync loads widget scripts lazily — only when the |
| 44 |
* picker opens or a widget that needs this script is about to mount. |
| 45 |
* wp_register_script() makes the handle known to WordPress without |
| 46 |
* actually outputting a <script> tag. The shell requests the script |
| 47 |
* via its own enqueue path at the right moment. Using wp_enqueue_script() |
| 48 |
* here would output the tag unconditionally on every admin page, wasting |
| 49 |
* bandwidth for users who never open the widget picker. |
| 50 |
* |
| 51 |
* WHY IS THE CSS REGISTERED HERE BUT ENQUEUED SEPARATELY? |
| 52 |
* Styles do not have a lazy-load mechanism equivalent to scripts. If we |
| 53 |
* registered the CSS but never enqueued it, the widget would flash as |
| 54 |
* unstyled on first mount while the stylesheet is fetched. So we register |
| 55 |
* both the JS and the CSS here, then eagerly enqueue just the CSS in a |
| 56 |
* separate function below that only runs on shell pages. |
| 57 |
* |
| 58 |
* SCRIPT_DEBUG CONVENTION |
| 59 |
* When SCRIPT_DEBUG is true WordPress loads the unminified development |
| 60 |
* build (widget-starter.js). In production it loads the minified build |
| 61 |
* (widget-starter.min.js). Both are produced by `npm run build:widget-starter`. |
| 62 |
* |
| 63 |
* VERSION STRINGS |
| 64 |
* Using filemtime() as the version means the browser cache is busted |
| 65 |
* automatically whenever the file changes on disk — no manual version bumping. |
| 66 |
* Falls back to OPENSTATION_VERSION if the file does not exist yet (e.g. |
| 67 |
* during development before the first build). |
| 68 |
*/ |
| 69 |
function openstation_register_starter_widget_assets() { |
| 70 |
$suffix = openstation_asset_suffix(); |
| 71 |
$version = defined( 'OPENSTATION_VERSION' ) ? OPENSTATION_VERSION : '0'; |
| 72 |
|
| 73 |
$js_path = OPENSTATION_DIR . 'assets/js/widget-starter' . $suffix . '.js'; |
| 74 |
$css_path = OPENSTATION_DIR . 'assets/js/widget-starter' . $suffix . '.css'; |
| 75 |
|
| 76 |
wp_register_style( |
| 77 |
'os-starter-widget', // Handle name — referenced in wp_enqueue_style() below. |
| 78 |
OPENSTATION_URL . 'assets/js/widget-starter' . $suffix . '.css', |
| 79 |
array(), // No CSS dependencies. |
| 80 |
file_exists( $css_path ) ? (string) filemtime( $css_path ) : $version |
| 81 |
); |
| 82 |
|
| 83 |
wp_register_script( |
| 84 |
'os-starter-widget', // Handle name — passed as 'script' to openstation_register_widget(). |
| 85 |
OPENSTATION_URL . 'assets/js/widget-starter' . $suffix . '.js', |
| 86 |
array( 'wp-api-fetch' ), // List WordPress script handles your widget depends on. |
| 87 |
// Declare every WordPress package you use, and do not assume any |
| 88 |
// of them are already on the page. They used to be: Core's ⌘K |
| 89 |
// palette pulled the whole Gutenberg runtime onto every admin |
| 90 |
// screen, so `wp.apiFetch`, `wp.element` and friends happened to |
| 91 |
// be globals by the time any widget mounted. Deferring that |
| 92 |
// runtime to the first ⌘K took the accident away — on a fresh |
| 93 |
// boot they are undefined until the palette is opened, and a |
| 94 |
// widget that reached for one at mount threw. A declared |
| 95 |
// dependency is resolved by WordPress when the script is |
| 96 |
// enqueued, which is why this line is the fix and not a |
| 97 |
// workaround. See docs/migration-wp-package-globals.md. |
| 98 |
file_exists( $js_path ) ? (string) filemtime( $js_path ) : $version, |
| 99 |
true // Load in the footer — always true for widget scripts. |
| 100 |
); |
| 101 |
} |
| 102 |
add_action( 'init', 'openstation_register_starter_widget_assets', 5 ); |
| 103 |
// Priority 5 — must run before openstation_register_starter_widget() at priority 6 |
| 104 |
// so the script handle exists when register_widget() looks it up. |
| 105 |
|
| 106 |
/** |
| 107 |
* Eagerly enqueue the CSS on OpenStation shell pages. |
| 108 |
* |
| 109 |
* The JS loads lazily (server-sync handles it). The CSS must load early |
| 110 |
* so it is in the DOM before the widget's first render — otherwise there |
| 111 |
* is a visible flash of unstyled content while the browser fetches the |
| 112 |
* stylesheet after mount. |
| 113 |
* |
| 114 |
* The two guards below prevent the stylesheet loading on pages where |
| 115 |
* OpenStation is not active: |
| 116 |
* openstation_is_enabled() — user has OpenStation turned on |
| 117 |
* openstation_is_chromeless_request() — not an iframe content request |
| 118 |
*/ |
| 119 |
function openstation_enqueue_starter_widget_styles() { |
| 120 |
if ( function_exists( 'openstation_is_enabled' ) && ! openstation_is_enabled() ) { |
| 121 |
return; |
| 122 |
} |
| 123 |
if ( function_exists( 'openstation_is_chromeless_request' ) && openstation_is_chromeless_request() ) { |
| 124 |
return; |
| 125 |
} |
| 126 |
wp_enqueue_style( 'os-starter-widget' ); |
| 127 |
} |
| 128 |
add_action( 'admin_enqueue_scripts', 'openstation_enqueue_starter_widget_styles', 20 ); |
| 129 |
|
| 130 |
/** |
| 131 |
* Announce the widget to OpenStation. |
| 132 |
* |
| 133 |
* This call stores the widget's metadata in OpenStation's registry so |
| 134 |
* it appears in the widget picker. The shell reads this registry at boot |
| 135 |
* and on every session refresh — adding or removing a widget definition |
| 136 |
* takes effect without a browser reload. |
| 137 |
* |
| 138 |
* AVAILABLE ARGUMENTS |
| 139 |
* ------------------- |
| 140 |
* label string Human-readable name shown in the picker. Required. |
| 141 |
* description string One-line subtitle shown beneath the label. |
| 142 |
* icon string Any dashicons class, e.g. 'dashicons-chart-bar'. |
| 143 |
* Full list: https://developer.wordpress.org/resource/dashicons/ |
| 144 |
* script string The wp_register_script() handle from above. |
| 145 |
* movable bool true = user can drag the widget off the right column |
| 146 |
* and float it anywhere on the desktop. |
| 147 |
* false = widget stays in the column (default). |
| 148 |
* resizable bool true = user can resize the widget card. |
| 149 |
* Requires movable: true for all 8 handles. |
| 150 |
* Column-docked widgets only get a bottom handle. |
| 151 |
* min_width int Smallest width the user can drag the card to (px). |
| 152 |
* min_height int Smallest height the user can drag the card to (px). |
| 153 |
* max_width int Optional ceiling on user-driven width resize (px). |
| 154 |
* max_height int Optional ceiling on user-driven height resize (px). |
| 155 |
* default_width int Starting width when first added as a floating widget. |
| 156 |
* default_height int Starting height when first added as a floating widget. |
| 157 |
* capabilities array Optional. ALL listed capabilities must be held by the |
| 158 |
* current user or the widget will not register for them. |
| 159 |
* e.g. array( 'edit_posts', 'publish_posts' ) |
| 160 |
*/ |
| 161 |
function openstation_register_starter_widget() { |
| 162 |
if ( ! function_exists( 'openstation_register_widget' ) ) { |
| 163 |
// OpenStation is not active — bail gracefully. |
| 164 |
return; |
| 165 |
} |
| 166 |
|
| 167 |
openstation_register_widget( |
| 168 |
// First argument: widget id. |
| 169 |
// Must exactly match the WIDGET_ID constant in your JS file and the |
| 170 |
// key used in window.openStationWidgets[ id ] at the bottom of index.ts. |
| 171 |
'desktop-mode/starter', |
| 172 |
array( |
| 173 |
'label' => __( 'Starter Widget', 'desktop-mode' ), |
| 174 |
'description' => __( 'A skeleton widget — copy this to build your own.', 'desktop-mode' ), |
| 175 |
'icon' => 'dashicons-welcome-widgets-menus', |
| 176 |
'script' => 'os-starter-widget', |
| 177 |
'movable' => true, |
| 178 |
'resizable' => true, |
| 179 |
'min_width' => 200, |
| 180 |
'min_height' => 140, |
| 181 |
'default_width' => 280, |
| 182 |
'default_height' => 200, |
| 183 |
) |
| 184 |
); |
| 185 |
} |
| 186 |
add_action( 'init', 'openstation_register_starter_widget', 6 ); |
| 187 |
// Priority 6 — runs after the asset registration at priority 5. |
| 188 |
|