← All changes
|
jetpack_vendor/automattic/jetpack-premium-analytics/src/dashboard-layout.php
+101
-528
16.2
→
16.3
View file →
| @@ -1,10 +1,12 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Dashboard Layout: Premium Analytics server-side defaults. | |
| 3 | + * Dashboard Layout: the default-layout primitives shared by the section API and its registrants. | |
| 4 | 4 | * |
| 5 | - * Ships its own default layout rather than the core dashboard endpoint (Gutenberg-only, returns | |
| 6 | - * core's widgets); the frontend reads the default from the section entity, so nothing is server-seeded. | |
| 5 | + * A section declares its default layout when it registers, and | |
| 6 | + * Dashboard_Section::get_default_layout() runs it through DASHBOARD_DEFAULT_LAYOUT_FILTER: the | |
| 7 | + * package drops the widget types the site cannot serve there, and a plugin may add its own | |
| 8 | + * instances to any section. | |
| 7 | 9 | * |
| 8 | 10 | * @package automattic/jetpack-premium-analytics |
| 9 | 11 | */ |
| 10 | 12 | |
| @@ -9,10 +11,8 @@ | ||
| 9 | 11 | */ |
| 10 | 12 | |
| 11 | 13 | namespace Automattic\Jetpack\PremiumAnalytics; |
| 12 | 14 | |
| 13 | -require_once __DIR__ . '/dashboard-grammar.php'; | |
| 14 | -require_once __DIR__ . '/rest-namespace.php'; | |
| 15 | 15 | // Availability policy for default layout instances: defaults are read outside |
| 16 | 16 | // the widget registry bootstrap, so the policy must be loaded here explicitly. |
| 17 | 17 | require_once __DIR__ . '/widget-type-support.php'; |
| 18 | 18 | |
| @@ -18,134 +18,21 @@ | ||
| 18 | 18 | |
| 19 | 19 | /** |
| 20 | 20 | * Identifier of the Premium Analytics dashboard, formatted as `<plugin>_<page>` |
| 21 | 21 | * to match the underscore form produced by the wp-build pipeline. Used as the |
| 22 | - * `{name}` segment of the REST route. | |
| 22 | + * `{name}` segment of the sections REST route. | |
| 23 | 23 | */ |
| 24 | 24 | const DASHBOARD_NAME = 'jetpack-premium-analytics_dashboard'; |
| 25 | 25 | |
| 26 | 26 | /** |
| 27 | - * Filter through which the default layout for a dashboard is resolved. | |
| 27 | + * Filter through which a section's default layout is resolved. Documented where it | |
| 28 | + * runs, in Dashboard_Section::get_default_layout(). | |
| 28 | 29 | */ |
| 29 | 30 | const DASHBOARD_DEFAULT_LAYOUT_FILTER = 'jetpack_premium_analytics_dashboard_default_layout'; |
| 30 | 31 | |
| 31 | 32 | /** |
| 32 | - * Preference keys used by the dashboard route's tabbed sections. | |
| 33 | - */ | |
| 34 | -const DASHBOARD_TRAFFIC_SECTION_ID = 'traffic'; | |
| 35 | -const DASHBOARD_INSIGHTS_SECTION_ID = 'insights'; | |
| 36 | -const DASHBOARD_SUBSCRIBERS_SECTION_ID = 'subscribers'; | |
| 37 | -const DASHBOARD_STORE_SECTION_ID = 'store'; | |
| 38 | -const DASHBOARD_ADS_SECTION_ID = 'ads'; | |
| 39 | - | |
| 40 | -/** | |
| 41 | - * Resolves the default layout registered for a dashboard. | |
| 33 | + * Builds a widget instance for a section's default layout. | |
| 42 | 34 | * |
| 43 | - * Returns a fresh evaluation of the filter chain each call, so callers always | |
| 44 | - * see the current code default rather than a hydrated copy. | |
| 45 | - * | |
| 46 | - * @param string $dashboard_name Identifier of the dashboard or dashboard section. | |
| 47 | - * @return array Array of widget instances (possibly empty). | |
| 48 | - */ | |
| 49 | -function get_dashboard_default_layout_for( $dashboard_name ) { | |
| 50 | - /** | |
| 51 | - * Filters the default dashboard layout served to users who have not | |
| 52 | - * customized theirs. | |
| 53 | - * | |
| 54 | - * Each entry should match the dashboard's widget instance shape: `uuid`, | |
| 55 | - * `type`, optional `attributes`, optional `placement`. | |
| 56 | - * | |
| 57 | - * @param array $default_layout Default array of widget instances. | |
| 58 | - * @param string $dashboard_name Identifier of the dashboard or dashboard | |
| 59 | - * section receiving the default. Callbacks | |
| 60 | - * targeting a specific default should switch | |
| 61 | - * on this value. | |
| 62 | - */ | |
| 63 | - $default = apply_filters( DASHBOARD_DEFAULT_LAYOUT_FILTER, array(), $dashboard_name ); | |
| 64 | - | |
| 65 | - return remove_unsupported_widget_items( | |
| 66 | - is_array( $default ) ? array_values( $default ) : array(), | |
| 67 | - 'type', | |
| 68 | - get_widget_support_context() | |
| 69 | - ); | |
| 70 | -} | |
| 71 | - | |
| 72 | -/** | |
| 73 | - * Returns availability gates for conditional dashboard tabs. | |
| 74 | - * | |
| 75 | - * The callbacks are used directly because the section registry may not be | |
| 76 | - * initialized when this route runs. | |
| 77 | - * | |
| 78 | - * @since 0.3.0 | |
| 79 | - * | |
| 80 | - * @return callable[] Resolved tab ID mapped to its availability callback. | |
| 81 | - */ | |
| 82 | -function get_dashboard_default_layout_gates() { | |
| 83 | - return array( | |
| 84 | - DASHBOARD_STORE_SECTION_ID => array( Capabilities::class, 'current_user_can_view_store_reports' ), | |
| 85 | - DASHBOARD_SUBSCRIBERS_SECTION_ID => __NAMESPACE__ . '\\is_subscribers_dashboard_section_available', | |
| 86 | - DASHBOARD_ADS_SECTION_ID => __NAMESPACE__ . '\\is_ads_dashboard_section_available_to_current_user', | |
| 87 | - ); | |
| 88 | -} | |
| 89 | - | |
| 90 | -/** | |
| 91 | - * REST callback returning the default layout for the requested dashboard. | |
| 92 | - * | |
| 93 | - * Availability is checked after resolving the name because tabs can be | |
| 94 | - * requested by alias or full section ID. | |
| 95 | - * | |
| 96 | - * @param \WP_REST_Request $request REST request carrying the dashboard name. | |
| 97 | - * @return \WP_REST_Response|\WP_Error Response wrapping the default layout array. | |
| 98 | - */ | |
| 99 | -function get_dashboard_default_layout_response( $request ) { | |
| 100 | - $dashboard_name = $request['name']; | |
| 101 | - $gates = get_dashboard_default_layout_gates(); | |
| 102 | - $section_id = get_dashboard_default_section_id_for( $dashboard_name ); | |
| 103 | - | |
| 104 | - $out_of_preview = null !== $section_id | |
| 105 | - && ! is_dashboard_section_in_preview_scope( DASHBOARD_NAME, $section_id ); | |
| 106 | - | |
| 107 | - $gate_fails = null !== $section_id | |
| 108 | - && isset( $gates[ $section_id ] ) | |
| 109 | - && ! call_user_func( $gates[ $section_id ] ); | |
| 110 | - | |
| 111 | - if ( $out_of_preview || $gate_fails ) { | |
| 112 | - return new \WP_Error( | |
| 113 | - 'dashboard_section_unavailable', | |
| 114 | - __( 'Dashboard section is not available.', 'jetpack-premium-analytics-pkg' ), | |
| 115 | - array( 'status' => 404 ) | |
| 116 | - ); | |
| 117 | - } | |
| 118 | - | |
| 119 | - return rest_ensure_response( get_dashboard_default_layout_for( $dashboard_name ) ); | |
| 120 | -} | |
| 121 | - | |
| 122 | -/** | |
| 123 | - * Registers the REST route that exposes per-dashboard default layouts. | |
| 124 | - * | |
| 125 | - * @return void | |
| 126 | - */ | |
| 127 | -function register_dashboard_default_layout_route() { | |
| 128 | - register_rest_route( | |
| 129 | - DASHBOARD_REST_NAMESPACE, | |
| 130 | - '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/default-layout', | |
| 131 | - array( | |
| 132 | - 'methods' => \WP_REST_Server::READABLE, | |
| 133 | - 'callback' => __NAMESPACE__ . '\\get_dashboard_default_layout_response', | |
| 134 | - 'permission_callback' => array( Capabilities::class, 'current_user_can_view_analytics' ), | |
| 135 | - 'args' => array( | |
| 136 | - 'name' => array( | |
| 137 | - 'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics-pkg' ), | |
| 138 | - 'type' => 'string', | |
| 139 | - ), | |
| 140 | - ), | |
| 141 | - ) | |
| 142 | - ); | |
| 143 | -} | |
| 144 | - | |
| 145 | -/** | |
| 146 | - * Builds a widget instance for bundled dashboard defaults. | |
| 147 | - * | |
| 148 | 35 | * @param string $uuid Widget instance UUID. |
| 149 | 36 | * @param string $type Widget type. |
| 150 | 37 | * @param int $order Widget placement order. |
| 151 | 38 | * @param int $width Widget placement width. |
| @@ -179,431 +66,117 @@ | ||
| 179 | 66 | return $widget; |
| 180 | 67 | } |
| 181 | 68 | |
| 182 | 69 | /** |
| 183 | - * Returns the bundled default widget layouts keyed by dashboard tab. | |
| 70 | + * Drops the widget instances the site cannot serve from a section's default layout. | |
| 184 | 71 | * |
| 185 | - * @return array Map of tab IDs to widget layout arrays. | |
| 72 | + * A persisted layout keeps such an instance as a removable ghost widget; a default must not | |
| 73 | + * seed one. Hooked late, after the callbacks that add instances, so it covers those too. | |
| 74 | + * | |
| 75 | + * @param array $layout Default widget instances. | |
| 76 | + * @return array The layout minus the unsupported instances. | |
| 186 | 77 | */ |
| 187 | -function get_dashboard_default_section_layouts() { | |
| 188 | - return array( | |
| 189 | - DASHBOARD_TRAFFIC_SECTION_ID => array( | |
| 190 | - // Rows fill the three-column grid in the prototype's order. Plan usage | |
| 191 | - // is intentionally not a default; it stays available from the widget | |
| 192 | - // picker. | |
| 193 | - // Row 1: traffic chart. | |
| 194 | - get_dashboard_default_widget_instance( | |
| 195 | - 'default-traffic-chart-widget-instance', | |
| 196 | - 'jpa/traffic-chart', | |
| 197 | - 0, | |
| 198 | - 3, | |
| 199 | - 2 | |
| 200 | - ), | |
| 201 | - // Row 2: most-viewed posts + referrers + devices. | |
| 202 | - get_dashboard_default_widget_instance( | |
| 203 | - 'default-stats-top-posts-widget-instance', | |
| 204 | - 'jpa/stats-top-posts', | |
| 205 | - 1, | |
| 206 | - 1, | |
| 207 | - 2 | |
| 208 | - ), | |
| 209 | - get_dashboard_default_widget_instance( | |
| 210 | - 'default-referrers-widget-instance', | |
| 211 | - 'jpa/referrers', | |
| 212 | - 2, | |
| 213 | - 1, | |
| 214 | - 2 | |
| 215 | - ), | |
| 216 | - get_dashboard_default_widget_instance( | |
| 217 | - 'default-devices-widget-instance', | |
| 218 | - 'jpa/devices', | |
| 219 | - 3, | |
| 220 | - 1, | |
| 221 | - 2 | |
| 222 | - ), | |
| 223 | - // Row 3: locations map + top platforms. | |
| 224 | - get_dashboard_default_widget_instance( | |
| 225 | - 'default-locations-widget-instance', | |
| 226 | - 'jpa/locations', | |
| 227 | - 4, | |
| 228 | - 2, | |
| 229 | - 2 | |
| 230 | - ), | |
| 231 | - get_dashboard_default_widget_instance( | |
| 232 | - 'default-top-platforms-widget-instance', | |
| 233 | - 'jpa/top-platforms', | |
| 234 | - 5, | |
| 235 | - 1, | |
| 236 | - 2 | |
| 237 | - ), | |
| 238 | - // Row 4: UTM insights + clicks + VideoPress (sites running VideoPress only). | |
| 239 | - get_dashboard_default_widget_instance( | |
| 240 | - 'default-utm-insights-widget-instance', | |
| 241 | - 'jpa/utm-insights', | |
| 242 | - 6, | |
| 243 | - 1, | |
| 244 | - 2, | |
| 245 | - array( | |
| 246 | - 'utmDimension' => 'utm_source,utm_medium', | |
| 247 | - ) | |
| 248 | - ), | |
| 249 | - get_dashboard_default_widget_instance( | |
| 250 | - 'default-clicks-widget-instance', | |
| 251 | - 'jpa/clicks', | |
| 252 | - 7, | |
| 253 | - 1, | |
| 254 | - 2 | |
| 255 | - ), | |
| 256 | - get_dashboard_default_widget_instance( | |
| 257 | - 'default-videopress-widget-instance', | |
| 258 | - 'jpa/videopress', | |
| 259 | - 8, | |
| 260 | - 1, | |
| 261 | - 2 | |
| 262 | - ), | |
| 263 | - // Row 5: authors + search terms + file downloads (Simple only). | |
| 264 | - get_dashboard_default_widget_instance( | |
| 265 | - 'default-authors-widget-instance', | |
| 266 | - 'jpa/authors', | |
| 267 | - 9, | |
| 268 | - 1, | |
| 269 | - 2 | |
| 270 | - ), | |
| 271 | - get_dashboard_default_widget_instance( | |
| 272 | - 'default-search-terms-widget-instance', | |
| 273 | - 'jpa/search-terms', | |
| 274 | - 10, | |
| 275 | - 1, | |
| 276 | - 2 | |
| 277 | - ), | |
| 278 | - get_dashboard_default_widget_instance( | |
| 279 | - 'default-file-downloads-widget-instance', | |
| 280 | - 'jpa/file-downloads', | |
| 281 | - 11, | |
| 282 | - 1, | |
| 283 | - 2 | |
| 284 | - ), | |
| 285 | - ), | |
| 286 | - DASHBOARD_INSIGHTS_SECTION_ID => array( | |
| 287 | - // Follows the prototype's rows (WOOA7S-1786). Emails lives on the | |
| 288 | - // Subscribers tab, so that row is absent. | |
| 289 | - // Row 1: highlights banner. | |
| 290 | - get_dashboard_default_widget_instance( | |
| 291 | - 'default-annual-highlights-widget-instance', | |
| 292 | - 'jpa/annual-highlights', | |
| 293 | - 0, | |
| 294 | - 4, | |
| 295 | - 1 | |
| 296 | - ), | |
| 297 | - // Row 2: the at-a-glance cards, as the design pairs them. All three are | |
| 298 | - // two rows tall so their display-sized figures fit without scrolling: a | |
| 299 | - // 1x1 tile is 200px, which the two-field cards overflow. WOOA7S-2009 | |
| 300 | - // settles the final widths. | |
| 301 | - get_dashboard_default_widget_instance( | |
| 302 | - 'default-all-time-stats-widget-instance', | |
| 303 | - 'jpa/all-time-stats', | |
| 304 | - 1, | |
| 305 | - 2, | |
| 306 | - 2, | |
| 307 | - array( | |
| 308 | - // The design shows three totals; the widget's own default adds | |
| 309 | - // Comments, which the comment leaderboards below already cover. | |
| 310 | - 'metrics' => array( 'views', 'visitors', 'posts' ), | |
| 311 | - ) | |
| 312 | - ), | |
| 313 | - get_dashboard_default_widget_instance( | |
| 314 | - 'default-most-popular-day-widget-instance', | |
| 315 | - 'jpa/most-popular-day', | |
| 316 | - 2, | |
| 317 | - 1, | |
| 318 | - 2 | |
| 319 | - ), | |
| 320 | - get_dashboard_default_widget_instance( | |
| 321 | - 'default-most-popular-time-widget-instance', | |
| 322 | - 'jpa/most-popular-time', | |
| 323 | - 3, | |
| 324 | - 1, | |
| 325 | - 2 | |
| 326 | - ), | |
| 327 | - // Row 3: posting-activity heatmap. | |
| 328 | - get_dashboard_default_widget_instance( | |
| 329 | - 'default-posting-activity-widget-instance', | |
| 330 | - 'jpa/posting-activity', | |
| 331 | - 4, | |
| 332 | - 4, | |
| 333 | - 1 | |
| 334 | - ), | |
| 335 | - // Row 4: the two post spotlights. | |
| 336 | - get_dashboard_default_widget_instance( | |
| 337 | - 'default-latest-post-widget-instance', | |
| 338 | - 'jpa/latest-post', | |
| 339 | - 5, | |
| 340 | - 2, | |
| 341 | - 2 | |
| 342 | - ), | |
| 343 | - get_dashboard_default_widget_instance( | |
| 344 | - 'default-popular-post-widget-instance', | |
| 345 | - 'jpa/popular-post', | |
| 346 | - 6, | |
| 347 | - 2, | |
| 348 | - 2 | |
| 349 | - ), | |
| 350 | - // Row 5: the period totals and the weekday and hour-of-day | |
| 351 | - // distributions. | |
| 352 | - get_dashboard_default_widget_instance( | |
| 353 | - 'default-total-views-widget-instance', | |
| 354 | - 'jpa/total-views', | |
| 355 | - 7, | |
| 356 | - 1, | |
| 357 | - 1 | |
| 358 | - ), | |
| 359 | - get_dashboard_default_widget_instance( | |
| 360 | - 'default-total-visitors-widget-instance', | |
| 361 | - 'jpa/total-visitors', | |
| 362 | - 8, | |
| 363 | - 1, | |
| 364 | - 1 | |
| 365 | - ), | |
| 366 | - get_dashboard_default_widget_instance( | |
| 367 | - 'default-popular-days-widget-instance', | |
| 368 | - 'jpa/popular-days', | |
| 369 | - 9, | |
| 370 | - 1, | |
| 371 | - 1 | |
| 372 | - ), | |
| 373 | - get_dashboard_default_widget_instance( | |
| 374 | - 'default-popular-hours-widget-instance', | |
| 375 | - 'jpa/popular-hours', | |
| 376 | - 10, | |
| 377 | - 1, | |
| 378 | - 1 | |
| 379 | - ), | |
| 380 | - // Row 6: daily views heatmap. Two rows tall, as in the prototype: cells are sized | |
| 381 | - // from the tile's height, and only here do they fit each day's view count. | |
| 382 | - get_dashboard_default_widget_instance( | |
| 383 | - 'default-traffic-views-activity-widget-instance', | |
| 384 | - 'jpa/traffic-views-activity', | |
| 385 | - 11, | |
| 386 | - 4, | |
| 387 | - 2 | |
| 388 | - ), | |
| 389 | - // Row 7: the comment leaderboards, shares, and tags. | |
| 390 | - get_dashboard_default_widget_instance( | |
| 391 | - 'default-most-commented-posts-widget-instance', | |
| 392 | - 'jpa/most-commented-posts', | |
| 393 | - 12, | |
| 394 | - 1, | |
| 395 | - 2 | |
| 396 | - ), | |
| 397 | - get_dashboard_default_widget_instance( | |
| 398 | - 'default-most-commented-authors-widget-instance', | |
| 399 | - 'jpa/most-commented-authors', | |
| 400 | - 13, | |
| 401 | - 1, | |
| 402 | - 2 | |
| 403 | - ), | |
| 404 | - get_dashboard_default_widget_instance( | |
| 405 | - 'default-shares-widget-instance', | |
| 406 | - 'jpa/shares', | |
| 407 | - 14, | |
| 408 | - 1, | |
| 409 | - 2 | |
| 410 | - ), | |
| 411 | - get_dashboard_default_widget_instance( | |
| 412 | - 'default-tags-widget-instance', | |
| 413 | - 'jpa/tags', | |
| 414 | - 15, | |
| 415 | - 1, | |
| 416 | - 2 | |
| 417 | - ), | |
| 418 | - ), | |
| 419 | - DASHBOARD_SUBSCRIBERS_SECTION_ID => array( | |
| 420 | - // Subscriber highlights is intentionally not a default: the design | |
| 421 | - // opens on the chart. It stays available from the widget picker. | |
| 422 | - // Row 1: subscribers chart. | |
| 423 | - get_dashboard_default_widget_instance( | |
| 424 | - 'default-subscribers-chart-widget-instance', | |
| 425 | - 'jpa/subscribers-chart', | |
| 426 | - 0, | |
| 427 | - 4, | |
| 428 | - 2 | |
| 429 | - ), | |
| 430 | - // Row 2: latest subscribers + latest emails sent. | |
| 431 | - get_dashboard_default_widget_instance( | |
| 432 | - 'default-subscribers-list-widget-instance', | |
| 433 | - 'jpa/subscribers-list', | |
| 434 | - 1, | |
| 435 | - 2, | |
| 436 | - 2 | |
| 437 | - ), | |
| 438 | - get_dashboard_default_widget_instance( | |
| 439 | - 'default-subscribers-emails-widget-instance', | |
| 440 | - 'jpa/stats-emails', | |
| 441 | - 2, | |
| 442 | - 2, | |
| 443 | - 2, | |
| 444 | - array( | |
| 445 | - 'metric' => 'opens', | |
| 446 | - ) | |
| 447 | - ), | |
| 448 | - ), | |
| 449 | - DASHBOARD_STORE_SECTION_ID => array( | |
| 450 | - get_dashboard_default_widget_instance( | |
| 451 | - 'default-store-performance-widget-instance', | |
| 452 | - 'jpa/store-performance', | |
| 453 | - 0, | |
| 454 | - 2, | |
| 455 | - 1 | |
| 456 | - ), | |
| 457 | - get_dashboard_default_widget_instance( | |
| 458 | - 'default-total-sales-over-time-widget-instance', | |
| 459 | - 'jpa/total-sales-over-time', | |
| 460 | - 1, | |
| 461 | - 1, | |
| 462 | - 1 | |
| 463 | - ), | |
| 464 | - get_dashboard_default_widget_instance( | |
| 465 | - 'default-conversion-rate-widget-instance', | |
| 466 | - 'jpa/conversion-rate', | |
| 467 | - 2, | |
| 468 | - 1, | |
| 469 | - 1 | |
| 470 | - ), | |
| 471 | - get_dashboard_default_widget_instance( | |
| 472 | - 'default-orders-over-time-widget-instance', | |
| 473 | - 'jpa/orders-over-time', | |
| 474 | - 3, | |
| 475 | - 1, | |
| 476 | - 1 | |
| 477 | - ), | |
| 478 | - get_dashboard_default_widget_instance( | |
| 479 | - 'default-average-order-value-widget-instance', | |
| 480 | - 'jpa/average-order-value', | |
| 481 | - 4, | |
| 482 | - 1, | |
| 483 | - 1 | |
| 484 | - ), | |
| 485 | - get_dashboard_default_widget_instance( | |
| 486 | - 'default-top-performing-products-widget-instance', | |
| 487 | - 'jpa/top-performing-products', | |
| 488 | - 5, | |
| 489 | - 1, | |
| 490 | - 1 | |
| 491 | - ), | |
| 492 | - get_dashboard_default_widget_instance( | |
| 493 | - 'default-new-vs-returning-customer-widget-instance', | |
| 494 | - 'jpa/new-vs-returning-customer', | |
| 495 | - 6, | |
| 496 | - 1, | |
| 497 | - 1 | |
| 498 | - ), | |
| 499 | - get_dashboard_default_widget_instance( | |
| 500 | - 'default-payment-status-widget-instance', | |
| 501 | - 'jpa/payment-status', | |
| 502 | - 7, | |
| 503 | - 1, | |
| 504 | - 1 | |
| 505 | - ), | |
| 506 | - get_dashboard_default_widget_instance( | |
| 507 | - 'default-orders-fulfillment-widget-instance', | |
| 508 | - 'jpa/orders-fulfillment', | |
| 509 | - 8, | |
| 510 | - 1, | |
| 511 | - 1 | |
| 512 | - ), | |
| 513 | - ), | |
| 514 | - DASHBOARD_ADS_SECTION_ID => array( | |
| 515 | - // Match the Calypso WordAds widget order. | |
| 516 | - get_dashboard_default_widget_instance( | |
| 517 | - 'default-wordads-highlights-widget-instance', | |
| 518 | - 'jpa/wordads-highlights', | |
| 519 | - 0, | |
| 520 | - 4, | |
| 521 | - 1 | |
| 522 | - ), | |
| 523 | - get_dashboard_default_widget_instance( | |
| 524 | - 'default-wordads-chart-tabs-widget-instance', | |
| 525 | - 'jpa/wordads-chart-tabs', | |
| 526 | - 1, | |
| 527 | - 4, | |
| 528 | - 2 | |
| 529 | - ), | |
| 530 | - get_dashboard_default_widget_instance( | |
| 531 | - 'default-wordads-earnings-history-widget-instance', | |
| 532 | - 'jpa/wordads-earnings-history', | |
| 533 | - 2, | |
| 534 | - 4, | |
| 535 | - 2 | |
| 536 | - ), | |
| 537 | - get_dashboard_default_widget_instance( | |
| 538 | - 'default-wordads-sponsored-content-history-widget-instance', | |
| 539 | - 'jpa/wordads-sponsored-content-history', | |
| 540 | - 3, | |
| 541 | - 2, | |
| 542 | - 2 | |
| 543 | - ), | |
| 544 | - get_dashboard_default_widget_instance( | |
| 545 | - 'default-wordads-adjustments-history-widget-instance', | |
| 546 | - 'jpa/wordads-adjustments-history', | |
| 547 | - 4, | |
| 548 | - 2, | |
| 549 | - 2 | |
| 550 | - ), | |
| 551 | - ), | |
| 78 | +function remove_unsupported_default_layout_items( $layout ) { | |
| 79 | + $layout = remove_unsupported_widget_items( | |
| 80 | + is_array( $layout ) ? $layout : array(), | |
| 81 | + 'type', | |
| 82 | + get_widget_support_context() | |
| 552 | 83 | ); |
| 84 | + | |
| 85 | + return remove_unregistered_default_layout_items( $layout ); | |
| 553 | 86 | } |
| 87 | +add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\remove_unsupported_default_layout_items', 100 ); | |
| 554 | 88 | |
| 555 | 89 | /** |
| 556 | - * Resolves a dashboard or section identifier to one of the bundled tab IDs. | |
| 90 | + * The widget type registry once it can answer, or null before that. | |
| 557 | 91 | * |
| 558 | - * @param string $dashboard_name Dashboard or section identifier. | |
| 559 | - * @return string|null Bundled tab ID, or null when the identifier is unsupported. | |
| 92 | + * It cannot answer before `init`, without the widget type API loaded, or with nothing registered, | |
| 93 | + * which is a checkout without a build. | |
| 94 | + * | |
| 95 | + * @since 0.11.0 | |
| 96 | + * | |
| 97 | + * @return Widget_Type_Registry|null | |
| 560 | 98 | */ |
| 561 | -function get_dashboard_default_section_id_for( $dashboard_name ) { | |
| 562 | - $aliases = array( | |
| 563 | - DASHBOARD_NAME => DASHBOARD_TRAFFIC_SECTION_ID, | |
| 564 | - DASHBOARD_TRAFFIC_SECTION_ID => DASHBOARD_TRAFFIC_SECTION_ID, | |
| 565 | - 'analytics/traffic' => DASHBOARD_TRAFFIC_SECTION_ID, | |
| 566 | - DASHBOARD_INSIGHTS_SECTION_ID => DASHBOARD_INSIGHTS_SECTION_ID, | |
| 567 | - 'analytics/insights' => DASHBOARD_INSIGHTS_SECTION_ID, | |
| 568 | - DASHBOARD_SUBSCRIBERS_SECTION_ID => DASHBOARD_SUBSCRIBERS_SECTION_ID, | |
| 569 | - 'analytics/subscribers' => DASHBOARD_SUBSCRIBERS_SECTION_ID, | |
| 570 | - DASHBOARD_STORE_SECTION_ID => DASHBOARD_STORE_SECTION_ID, | |
| 571 | - 'woocommerce/store' => DASHBOARD_STORE_SECTION_ID, | |
| 572 | - DASHBOARD_ADS_SECTION_ID => DASHBOARD_ADS_SECTION_ID, | |
| 573 | - 'analytics/ads' => DASHBOARD_ADS_SECTION_ID, | |
| 574 | - ); | |
| 99 | +function get_answering_widget_type_registry() { | |
| 100 | + if ( ! did_action( 'init' ) || ! function_exists( __NAMESPACE__ . '\\ensure_widget_registry_ready' ) ) { | |
| 101 | + return null; | |
| 102 | + } | |
| 575 | 103 | |
| 576 | - return $aliases[ $dashboard_name ] ?? null; | |
| 104 | + ensure_widget_registry_ready(); | |
| 105 | + $registry = Widget_Type_Registry::get_instance(); | |
| 106 | + | |
| 107 | + return $registry->get_all_registered() ? $registry : null; | |
| 577 | 108 | } |
| 578 | 109 | |
| 579 | 110 | /** |
| 580 | - * Seeds the bundled default layouts for the Premium Analytics dashboard tabs. | |
| 111 | + * Renames the widget instances whose type is a former name of a registered widget type. | |
| 581 | 112 | * |
| 582 | - * Only contributes to known Premium Analytics dashboard/tab aliases, leaving other dashboards | |
| 583 | - * untouched so the filter stays reusable if more dashboards are added later. | |
| 113 | + * Hooked before the unregistered-type check, so an instance a plugin still adds under an old | |
| 114 | + * name survives it under the current one. | |
| 584 | 115 | * |
| 585 | - * @param array $dashboard_layout Default layout from earlier callbacks. | |
| 586 | - * @param string $dashboard_name Identifier of the dashboard receiving the | |
| 587 | - * default. | |
| 588 | - * @return array The layout extended with the bundled widget instances. | |
| 116 | + * @since 0.11.0 | |
| 117 | + * | |
| 118 | + * @param array $layout Default widget instances. | |
| 119 | + * @return array The layout with current type names. | |
| 589 | 120 | */ |
| 590 | -function seed_default_dashboard_layout( $dashboard_layout, $dashboard_name = '' ) { | |
| 591 | - $section_id = get_dashboard_default_section_id_for( $dashboard_name ); | |
| 592 | - | |
| 593 | - if ( null === $section_id ) { | |
| 594 | - return $dashboard_layout; | |
| 121 | +function resolve_former_widget_types_in_default_layout( $layout ) { | |
| 122 | + $registry = get_answering_widget_type_registry(); | |
| 123 | + if ( ! $registry || ! is_array( $layout ) ) { | |
| 124 | + return $layout; | |
| 595 | 125 | } |
| 596 | 126 | |
| 597 | - $uuids = array_column( $dashboard_layout, 'uuid' ); | |
| 598 | - $layouts = get_dashboard_default_section_layouts(); | |
| 127 | + return array_map( | |
| 128 | + static function ( $item ) use ( $registry ) { | |
| 129 | + if ( is_array( $item ) && is_string( $item['type'] ?? null ) ) { | |
| 130 | + $item['type'] = $registry->resolve_name( $item['type'] ); | |
| 131 | + } | |
| 132 | + return $item; | |
| 133 | + }, | |
| 134 | + $layout | |
| 135 | + ); | |
| 136 | +} | |
| 137 | +add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\resolve_former_widget_types_in_default_layout', 99 ); | |
| 599 | 138 | |
| 600 | - foreach ( $layouts[ $section_id ] as $widget ) { | |
| 601 | - if ( ! in_array( $widget['uuid'], $uuids, true ) ) { | |
| 602 | - $dashboard_layout[] = $widget; | |
| 603 | - $uuids[] = $widget['uuid']; | |
| 604 | - } | |
| 139 | +/** | |
| 140 | + * Drops the widget instances whose type the widget type registry does not know. | |
| 141 | + * | |
| 142 | + * Only once the registry can answer: after `init`, with the widget type API loaded and at least | |
| 143 | + * one type registered. Before that, or on a checkout without a build, the default stays as | |
| 144 | + * declared rather than emptying itself. | |
| 145 | + * | |
| 146 | + * @since 0.9.0 | |
| 147 | + * | |
| 148 | + * @param array $layout Default widget instances. | |
| 149 | + * @return array The layout minus the instances of unregistered types. | |
| 150 | + */ | |
| 151 | +function remove_unregistered_default_layout_items( $layout ) { | |
| 152 | + $registry = get_answering_widget_type_registry(); | |
| 153 | + if ( ! $registry ) { | |
| 154 | + return $layout; | |
| 605 | 155 | } |
| 156 | + $registered = $registry->get_all_registered(); | |
| 606 | 157 | |
| 607 | - return $dashboard_layout; | |
| 158 | + return array_values( | |
| 159 | + array_filter( | |
| 160 | + $layout, | |
| 161 | + static function ( $item ) use ( $registered ) { | |
| 162 | + if ( ! is_array( $item ) ) { | |
| 163 | + return true; | |
| 164 | + } | |
| 165 | + // A non-string type, say the Widget_Type object register_widget_type() returns, is an | |
| 166 | + // unknown type, not a TypeError for the whole sections route. | |
| 167 | + $type = $item['type'] ?? ''; | |
| 168 | + return is_string( $type ) && isset( $registered[ $type ] ); | |
| 169 | + } | |
| 170 | + ) | |
| 171 | + ); | |
| 608 | 172 | } |
| 609 | -add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\seed_default_dashboard_layout', 10, 2 ); | |
| 173 | + | |
| 174 | +/** | |
| 175 | + * No-op kept for older copies of the package: they guard their include of this file on this | |
| 176 | + * symbol and call it from boot_routes(), so a newer copy loading first must still define it. | |
| 177 | + * | |
| 178 | + * @since 0.8.0 Registers nothing; the route it registered is gone. | |
| 179 | + * | |
| 180 | + * @return void | |
| 181 | + */ | |
| 182 | +function register_dashboard_default_layout_route() {} | |