PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.8
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.8
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 0.8.7 All 34 releases
desktop-mode / includes / content-graph / window.php

window.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.9.8, at includes/content-graph/window.php

258 lines 10.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Desktop Mode — Content Graph: window + desktop icon registration.
4 *
5 * Filterable surface (mirrors the my-wordpress module shape):
6 *
7 * - `desktop_mode_content_graph_window_args`
8 * - `desktop_mode_content_graph_icon_args`
9 * - `desktop_mode_content_graph_user_can_use`
10 * - `desktop_mode_content_graph_post_types`
11 * - `desktop_mode_content_graph_template_html`
12 *
13 * @package WPDesktopMode
14 */
15
16 defined( 'ABSPATH' ) || exit;
17
18 /**
19 * The Corkboard SVG, shared by the window icon and the desktop icon.
20 *
21 * Dashicons has no corkboard, and the near misses all fail for the
22 * same reason the old "Content Graph" name did: `networking` draws an
23 * org chart, `layout` draws a wireframe — diagrams of the data, not a
24 * thing on a desk. The pushpin is unavailable too, since
25 * `dashicons-admin-post` already owns it for Posts.
26 *
27 * So: a cork board with two notes pinned to it, joined by a length of
28 * thread. The thread is not decoration — it's the window's actual
29 * subject, the links between pieces of content, drawn the way anyone
30 * who has ever seen a detective's board already reads it. Without it
31 * the icon says "pinboard"; with it, "connections".
32 *
33 * Drawn in `currentColor`, which makes it a silhouette: `renderIcon()`
34 * paints it as a CSS mask rather than a background-image, so it takes
35 * whatever colour the surface is already using for text. That keeps it
36 * legible on the dark dock, on a light title bar, and on hover, with
37 * nothing to configure. Fixed colours could not do that — a background
38 * image has no colour to inherit.
39 *
40 * Hand-placed at 64×64 like the Games icons, the established shape for
41 * custom icons here. Held to five elements because it renders as small
42 * as 20px in the dock: board, two notes, thread, two pin heads. The
43 * thread arcs up through the empty top half rather than running
44 * between the notes, where a 5px gap would swallow it.
45 *
46 * @return string Raw `<svg>` markup.
47 */
48 function desktop_mode_content_graph_icon_svg() {
49 return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">'
50 // The board itself — outlined, so it frames rather than fills.
51 . '<rect x="5" y="9" width="54" height="46" rx="4.5" fill="none" stroke="currentColor" stroke-width="4"/>'
52 // Thread first: the pin heads below are drawn over its ends, so
53 // the joins stay round instead of showing a stroke cap.
54 . '<path d="M20.5 28Q32 16.5 43.5 28" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/>'
55 // Notes, each tilted a few degrees — pinned by hand, not laid out.
56 . '<g transform="rotate(-7 20.5 39)">'
57 . '<rect x="12" y="30" width="17" height="18" rx="1.5" fill="currentColor"/></g>'
58 . '<g transform="rotate(7 43.5 39)">'
59 . '<rect x="35" y="30" width="17" height="18" rx="1.5" fill="currentColor"/></g>'
60 // Pin heads, sitting proud of each note's top edge. They are the
61 // cue that separates a pinboard from a picture frame.
62 . '<circle cx="20.5" cy="28" r="3" fill="currentColor"/>'
63 . '<circle cx="43.5" cy="28" r="3" fill="currentColor"/>'
64 . '</svg>';
65 }
66
67 /**
68 * Whether the current user should see Content Graph.
69 *
70 * Mirrors the my-wordpress gate, anyone who can edit posts can view
71 * the link map of the content they author and maintain.
72 *
73 * @return bool
74 */
75 function desktop_mode_content_graph_user_can_use() {
76 $can = current_user_can( 'edit_posts' );
77
78 /**
79 * Filter whether the current user can see the Content Graph
80 * desktop icon and window.
81 *
82 * @param bool $can Default: edit_posts capability.
83 */
84 return (bool) apply_filters( 'desktop_mode_content_graph_user_can_use', $can );
85 }
86
87 /**
88 * Build the descriptor list of post types eligible for the graph.
89 * Defaults to every public post type (so CPTs participate by
90 * default), matching the user-facing filter bar that ships ON for all
91 * of them.
92 *
93 * @return array[] Each entry: `array( 'slug', 'label', 'icon', 'taxonomies' )`.
94 */
95 function desktop_mode_content_graph_post_types() {
96 $types = get_post_types( array( 'public' => true ), 'objects' );
97 $result = array();
98 foreach ( $types as $type ) {
99 // `attachment` is public but participates in the graph as media
100 // (rendered in the side panel) rather than as a node — skip it
101 // on the node-type axis to avoid every image becoming a node.
102 if ( 'attachment' === $type->name ) {
103 continue;
104 }
105 $result[] = array(
106 'slug' => (string) $type->name,
107 'label' => (string) $type->labels->name,
108 'icon' => (string) ( ! empty( $type->menu_icon ) ? $type->menu_icon : 'dashicons-admin-post' ),
109 'taxonomies' => array(
110 'category' => is_object_in_taxonomy( $type->name, 'category' ),
111 'post_tag' => is_object_in_taxonomy( $type->name, 'post_tag' ),
112 ),
113 );
114 }
115
116 /**
117 * Filter the list of post types shown in the Content Graph filter
118 * bar. Each entry declares `slug`, `label`, `icon`, and optionally
119 * `taxonomies` (`array( 'category' => bool, 'post_tag' => bool )`);
120 * entries missing `taxonomies` get it derived from
121 * `is_object_in_taxonomy()` after filtering. Removing
122 * an entry hides it from the filter bar AND excludes it from the
123 * graph entirely.
124 *
125 * @param array[] $result Default: every public post type except attachment.
126 */
127 $filtered = apply_filters( 'desktop_mode_content_graph_post_types', $result );
128 $filtered = is_array( $filtered ) ? array_values( $filtered ) : $result;
129
130 foreach ( $filtered as $i => $entry ) {
131 $slug = isset( $entry['slug'] ) ? (string) $entry['slug'] : '';
132 $filtered[ $i ]['taxonomies'] = array(
133 'category' => isset( $entry['taxonomies'] ) ? ! empty( $entry['taxonomies']['category'] ) : is_object_in_taxonomy( $slug, 'category' ),
134 'post_tag' => isset( $entry['taxonomies'] ) ? ! empty( $entry['taxonomies']['post_tag'] ) : is_object_in_taxonomy( $slug, 'post_tag' ),
135 );
136 }
137
138 return $filtered;
139 }
140
141 /**
142 * Render the Content Graph window's static template body. The bundle
143 * mounts its UI into `[data-desktop-mode-content-graph-root]`.
144 */
145 function desktop_mode_content_graph_render_template() {
146 ob_start();
147 ?>
148 <div class="desktop-mode-content-graph" data-desktop-mode-content-graph-root>
149 <header class="desktop-mode-content-graph__toolbar" data-desktop-mode-content-graph-toolbar></header>
150 <div class="desktop-mode-content-graph__body">
151 <div class="desktop-mode-content-graph__stage" data-desktop-mode-content-graph-stage>
152 <div class="desktop-mode-content-graph__loading" data-desktop-mode-content-graph-loading>
153 <wpd-spinner></wpd-spinner>
154 </div>
155 </div>
156 <aside class="desktop-mode-content-graph__panel" data-desktop-mode-content-graph-panel hidden></aside>
157 </div>
158 </div>
159 <?php
160 $html = (string) ob_get_clean();
161
162 /**
163 * Filter the Content Graph window's template HTML.
164 *
165 * @param string $html Default template HTML.
166 */
167 $filtered = (string) apply_filters( 'desktop_mode_content_graph_template_html', $html );
168
169 $allowed_html = function_exists( 'desktop_mode_native_window_allowed_html' )
170 ? desktop_mode_native_window_allowed_html()
171 : wp_kses_allowed_html( 'post' );
172
173 echo wp_kses( $filtered, $allowed_html );
174 }
175
176 /**
177 * Register the native window + the desktop icon on `init` priority 20,
178 * matching the my-wordpress + recycle-bin modules.
179 */
180 function desktop_mode_content_graph_register_window() {
181 if ( ! desktop_mode_content_graph_user_can_use() ) {
182 return;
183 }
184
185 $icon_uri = 'data:image/svg+xml;base64,' . base64_encode( desktop_mode_content_graph_icon_svg() );
186
187 $window_args = array(
188 'title' => __( 'Corkboard', 'desktop-mode' ),
189 'icon' => $icon_uri,
190 'template' => 'desktop_mode_content_graph_render_template',
191 'script' => 'desktop-mode-content-graph',
192 'style' => 'desktop-mode-content-graph',
193 'width' => 1080,
194 'height' => 720,
195 'min_width' => 720,
196 'min_height' => 480,
197 'placement' => 'none',
198 'config' => array(
199 'restRoot' => esc_url_raw( rest_url() ),
200 'restNonce' => wp_create_nonce( 'wp_rest' ),
201 'apiBase' => esc_url_raw( rest_url( 'desktop-mode/v1/content-graph' ) ),
202 'editPostUrl' => esc_url_raw( admin_url( 'post.php' ) ),
203 'editTermUrl' => esc_url_raw( admin_url( 'term.php' ) ),
204 'editUserUrl' => esc_url_raw( admin_url( 'user-edit.php' ) ),
205 'editCommentUrl' => esc_url_raw( admin_url( 'comment.php' ) ),
206 'mediaUrl' => esc_url_raw( admin_url( 'upload.php' ) ),
207 // Labels the "Open in <site>" action on the detail panel,
208 // which hands off to the site folder window.
209 'siteName' => desktop_mode_site_title(),
210 'postTypes' => desktop_mode_content_graph_post_types(),
211 ),
212 );
213
214 /**
215 * Filter the args used to register the Content Graph native window.
216 *
217 * @param array $window_args Args passed to `desktop_mode_register_window()`.
218 */
219 $window_args = (array) apply_filters( 'desktop_mode_content_graph_window_args', $window_args );
220
221 $registered = desktop_mode_register_window( 'desktop-mode-content-graph', $window_args );
222 if ( is_wp_error( $registered ) ) {
223 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
224 error_log( '[desktop-mode] Content Graph window registration failed: ' . $registered->get_error_message() );
225 return;
226 }
227
228 $icon_args = array(
229 'title' => __( 'Corkboard', 'desktop-mode' ),
230 'icon_svg' => desktop_mode_content_graph_icon_svg(),
231 'window' => 'desktop-mode-content-graph',
232 'pinned' => false,
233 'position' => 20,
234 );
235
236 /**
237 * Filter the args used to register the Content Graph desktop icon.
238 *
239 * @param array $icon_args Args passed to `desktop_mode_register_icon()`.
240 */
241 $icon_args = (array) apply_filters( 'desktop_mode_content_graph_icon_args', $icon_args );
242
243 desktop_mode_register_icon( 'desktop-mode-content-graph', $icon_args );
244 }
245 add_action( 'init', 'desktop_mode_content_graph_register_window', 20 );
246
247 /**
248 * Enqueue the bundle's CSS in admin context. The script is lazy-
249 * loaded by the native-window sync.
250 */
251 function desktop_mode_content_graph_enqueue_styles() {
252 if ( ! desktop_mode_content_graph_user_can_use() ) {
253 return;
254 }
255 wp_enqueue_style( 'desktop-mode-content-graph' );
256 }
257 add_action( 'admin_enqueue_scripts', 'desktop_mode_content_graph_enqueue_styles', 30 );
258