| 1 |
<?php |
| 2 |
|
| 3 |
namespace Templately\Modules\BlockPatterns; |
| 4 |
|
| 5 |
use Templately\Utils\Base; |
| 6 |
|
| 7 |
/** |
| 8 |
* "New from Templately" dashboard widget. |
| 9 |
* |
| 10 |
* The only surface in this module that reaches users BETWEEN editing sessions. |
| 11 |
* Renders exclusively from the local pattern-list cache — a dashboard visit |
| 12 |
* must never cost a remote call (spec 051 FR-009). Server-rendered: no bundle, |
| 13 |
* no REST (research D9). |
| 14 |
*/ |
| 15 |
class DashboardWidget extends Base { |
| 16 |
|
| 17 |
const WIDGET_ID = 'templately_block_patterns'; |
| 18 |
const MAX_ITEMS = 6; |
| 19 |
|
| 20 |
public function register_widget(): void { |
| 21 |
if ( ! current_user_can( 'edit_posts' ) ) { |
| 22 |
return; |
| 23 |
} |
| 24 |
|
| 25 |
// wp_dashboard_setup always runs with this loaded; the guard keeps the |
| 26 |
// method callable outside a dashboard request (tests, wp-cli). |
| 27 |
if ( ! function_exists( 'wp_add_dashboard_widget' ) ) { |
| 28 |
require_once ABSPATH . 'wp-admin/includes/dashboard.php'; |
| 29 |
} |
| 30 |
|
| 31 |
wp_add_dashboard_widget( |
| 32 |
self::WIDGET_ID, |
| 33 |
__( 'New from Templately', 'templately' ), |
| 34 |
[ $this, 'render' ] |
| 35 |
); |
| 36 |
} |
| 37 |
|
| 38 |
public function render(): void { |
| 39 |
$list = PatternSync::get_instance()->get_list(); |
| 40 |
$items = is_array( $list ) && ! empty( $list['items'] ) ? array_slice( $list['items'], 0, self::MAX_ITEMS ) : []; |
| 41 |
|
| 42 |
if ( empty( $items ) ) { |
| 43 |
// Cold cache is a normal state, not a failure — say nothing alarming. |
| 44 |
printf( |
| 45 |
'<p>%s</p>', |
| 46 |
esc_html__( 'Templately designs will appear here once your library has synced.', 'templately' ) |
| 47 |
); |
| 48 |
$this->render_library_link(); |
| 49 |
|
| 50 |
return; |
| 51 |
} |
| 52 |
|
| 53 |
echo '<ul>'; |
| 54 |
foreach ( $items as $item ) { |
| 55 |
if ( empty( $item['title'] ) ) { |
| 56 |
continue; |
| 57 |
} |
| 58 |
printf( |
| 59 |
'<li><a href="%1$s">%2$s</a></li>', |
| 60 |
esc_url( $this->library_url( $item['kind'] ?? 'section' ) ), |
| 61 |
esc_html( $item['title'] ) |
| 62 |
); |
| 63 |
} |
| 64 |
echo '</ul>'; |
| 65 |
|
| 66 |
$this->render_library_link(); |
| 67 |
} |
| 68 |
|
| 69 |
private function render_library_link(): void { |
| 70 |
printf( |
| 71 |
'<p><a href="%1$s">%2$s</a></p>', |
| 72 |
esc_url( $this->library_url( 'section' ) ), |
| 73 |
esc_html__( 'Browse the Templately library', 'templately' ) |
| 74 |
); |
| 75 |
} |
| 76 |
|
| 77 |
/** |
| 78 |
* Deep link into the SPA. The route string must match a path the app |
| 79 |
* actually registers (Constitution VIII, cross-module route contract) — |
| 80 |
* `?page=templately&path=/gutenberg/{type}` is that scheme. |
| 81 |
*/ |
| 82 |
private function library_url( string $kind ): string { |
| 83 |
$type = 'page' === $kind ? 'pages' : 'blocks'; |
| 84 |
|
| 85 |
return add_query_arg( |
| 86 |
[ |
| 87 |
'page' => 'templately', |
| 88 |
'path' => "/gutenberg/{$type}", |
| 89 |
], |
| 90 |
admin_url( 'admin.php' ) |
| 91 |
); |
| 92 |
} |
| 93 |
} |
| 94 |
|