PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.8
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.8
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 / my-wordpress / post-types.php

post-types.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.8, at includes/my-wordpress/post-types.php

285 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — My WordPress: custom post types as browsable folders.
4 *
5 * Every non-builtin post type the current user can edit becomes a
6 * section in the site window, grouped into a folder named after the
7 * plugin or theme that registered it (see `owner.php`). Types that
8 * expose themselves over the REST API are read straight from
9 * `wp/v2`; the rest go through the bridge controller in
10 * `rest-post-type.php`.
11 *
12 * Core's builtin types are excluded — `post`, `page`, and `attachment`
13 * are already root sections, and `wp_block` / `wp_template` /
14 * `wp_navigation` and friends are editor infrastructure rather than
15 * content someone browses. The `show_ui` requirement additionally
16 * excludes internal bookkeeping types, including OpenStation's own
17 * (`wpd_note`, agent conversations).
18 *
19 * Filterable surface:
20 *
21 * - `openstation_my_wordpress_post_types`
22 * - `openstation_my_wordpress_post_type_entity`
23 * - `openstation_my_wordpress_post_type_rest_enabled`
24 *
25 * @package OpenStation
26 */
27
28 defined( 'ABSPATH' ) || exit;
29
30 /**
31 * REST namespace the bridge controller registers non-REST post types
32 * under. Kept as a helper so the entity builder and the controller
33 * cannot drift apart.
34 *
35 * The VALUE keeps its pre-rebrand spelling on purpose: it is a
36 * persisted or externally-visible identifier, so renaming it would
37 * orphan data already written by live installs (or break a live
38 * URL). The mismatch between this constant's name and its value is
39 * deliberate — it is NOT a half-finished rename.
40 */
41 const OPENSTATION_MY_WORDPRESS_POST_TYPE_NAMESPACE = 'desktop-mode/v1';
42
43 /**
44 * Post types eligible to appear as sections in the site window.
45 *
46 * @return WP_Post_Type[] Keyed by post type name.
47 */
48 function openstation_my_wordpress_eligible_post_types() {
49 $types = array();
50
51 foreach ( get_post_types( array(), 'objects' ) as $name => $post_type ) {
52 if ( ! $post_type instanceof WP_Post_Type ) {
53 continue;
54 }
55 // Builtins are either already root sections (post, page,
56 // attachment) or editor infrastructure (wp_block, wp_template…).
57 if ( ! empty( $post_type->_builtin ) ) {
58 continue;
59 }
60 if ( empty( $post_type->show_ui ) ) {
61 continue;
62 }
63 if ( empty( $post_type->cap->edit_posts ) || ! current_user_can( $post_type->cap->edit_posts ) ) {
64 continue;
65 }
66 // A type that is neither REST-exposed nor bridgeable has no
67 // endpoint to browse — don't render a folder that can't open.
68 if ( empty( $post_type->show_in_rest ) && ! openstation_my_wordpress_post_type_is_bridged( $name ) ) {
69 continue;
70 }
71 $types[ $name ] = $post_type;
72 }
73
74 /**
75 * Filter the post type slugs shown as sections in the site window.
76 *
77 * Runs after the capability check, so anything still present here
78 * is already editable by the current user. Adding a slug that is
79 * neither `show_in_rest` nor bridged will produce a folder with no
80 * working endpoint.
81 *
82 * **Status: Experimental** — the eligibility rules may tighten as
83 * more type shapes are encountered in the wild.
84 *
85 * @param string[] $slugs Eligible post type slugs.
86 */
87 $slugs = apply_filters( 'openstation_my_wordpress_post_types', array_keys( $types ) );
88
89 $out = array();
90 foreach ( (array) $slugs as $slug ) {
91 $slug = (string) $slug;
92 if ( isset( $types[ $slug ] ) ) {
93 $out[ $slug ] = $types[ $slug ];
94 continue;
95 }
96 // Slug added by the filter — resolve it so callers always get
97 // real objects back.
98 $object = get_post_type_object( $slug );
99 if ( $object instanceof WP_Post_Type ) {
100 $out[ $slug ] = $object;
101 }
102 }
103
104 return $out;
105 }
106
107 /**
108 * Whether a post type should be served through the OpenStation bridge
109 * controller rather than `wp/v2`.
110 *
111 * Only ever true for types that opted out of the REST API. Those types
112 * are re-exposed under our own namespace behind a hard
113 * edit-capability gate — see `rest-post-type.php` for the reasoning.
114 *
115 * @param string $post_type Post type slug.
116 * @return bool
117 */
118 function openstation_my_wordpress_post_type_is_bridged( $post_type ) {
119 $object = get_post_type_object( (string) $post_type );
120 if ( ! $object instanceof WP_Post_Type ) {
121 return false;
122 }
123 if ( ! empty( $object->show_in_rest ) ) {
124 return false;
125 }
126
127 $enabled = ! empty( $object->show_ui ) && empty( $object->_builtin );
128
129 /**
130 * Filter whether a non-REST post type may be re-exposed through
131 * the OpenStation bridge route.
132 *
133 * The type's author set `show_in_rest => false`, so this is an
134 * opt-out worth honouring: return false to keep a type out of the
135 * site window entirely. The bridge is read-and-trash only and
136 * always requires the type's `edit_posts` capability, but a plugin
137 * that stores sensitive rows in a CPT may still prefer no endpoint
138 * at all.
139 *
140 * **Status: Experimental**
141 *
142 * @param bool $enabled Whether the bridge is allowed.
143 * @param string $post_type Post type slug.
144 */
145 return (bool) apply_filters( 'openstation_my_wordpress_post_type_rest_enabled', $enabled, (string) $post_type );
146 }
147
148 /**
149 * REST path a section should read from, relative to the REST root.
150 *
151 * @param WP_Post_Type $post_type Post type object.
152 * @return string REST path (e.g. `wp/v2/product`).
153 */
154 function openstation_my_wordpress_post_type_rest_path( $post_type ) {
155 if ( empty( $post_type->show_in_rest ) ) {
156 return OPENSTATION_MY_WORDPRESS_POST_TYPE_NAMESPACE . '/post-type/' . $post_type->name;
157 }
158 $namespace = ! empty( $post_type->rest_namespace ) ? $post_type->rest_namespace : 'wp/v2';
159 $base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
160 return $namespace . '/' . $base;
161 }
162
163 /**
164 * Icon for a post type section. `menu_icon` may be a dashicon class, an
165 * image URL, or a base64 data URI — the bundle's `renderIcon()` handles
166 * all three, so it passes through untouched.
167 *
168 * @param WP_Post_Type $post_type Post type object.
169 * @return string Icon reference.
170 */
171 function openstation_my_wordpress_post_type_icon( $post_type ) {
172 $icon = isset( $post_type->menu_icon ) ? (string) $post_type->menu_icon : '';
173 // `'none'` is the documented way to opt out of a menu icon and
174 // style it in CSS instead; treat it as absent.
175 if ( '' === $icon || 'none' === $icon || 'div' === $icon ) {
176 return 'dashicons-admin-post';
177 }
178 return $icon;
179 }
180
181 /**
182 * Post types that should carry OpenStation's own REST fields
183 * (`openstation_lock`, `openstation_contributors`,
184 * `openstation_attached_media`).
185 *
186 * That is every public REST-exposed type — the historical set — plus
187 * the types this module bridges, so a bridged section shows lock
188 * badges and attached media exactly like a `wp/v2` one.
189 *
190 * Call on `rest_api_init` or later; post type discovery must be
191 * complete.
192 *
193 * @return string[] Post type slugs.
194 */
195 function openstation_my_wordpress_rest_field_post_types() {
196 $types = get_post_types(
197 array(
198 'show_in_rest' => true,
199 'public' => true,
200 ),
201 'names'
202 );
203
204 foreach ( openstation_my_wordpress_eligible_post_types() as $name => $post_type ) {
205 if ( empty( $post_type->show_in_rest ) ) {
206 $types[ $name ] = $name;
207 }
208 }
209
210 return array_values( array_unique( $types ) );
211 }
212
213 /**
214 * Build the entity descriptor for one post type.
215 *
216 * @param WP_Post_Type $post_type Post type object.
217 * @return array Entity descriptor.
218 */
219 function openstation_my_wordpress_post_type_entity( $post_type ) {
220 $group = function_exists( 'openstation_my_wordpress_post_type_group' )
221 ? openstation_my_wordpress_post_type_group( $post_type->name )
222 : null;
223
224 $label = isset( $post_type->labels->name ) && '' !== $post_type->labels->name
225 ? (string) $post_type->labels->name
226 : (string) $post_type->name;
227
228 $entity = array(
229 'id' => 'cpt-' . $post_type->name,
230 'label' => $label,
231 'icon' => openstation_my_wordpress_post_type_icon( $post_type ),
232 'restPath' => openstation_my_wordpress_post_type_rest_path( $post_type ),
233 'kind' => 'post',
234 'post_type' => (string) $post_type->name,
235 'thumbnails' => post_type_supports( $post_type->name, 'thumbnail' ),
236 'group' => $group ? (string) $group['id'] : null,
237 'groupLabel' => $group ? (string) $group['label'] : null,
238 'groupIcon' => $group ? (string) $group['icon'] : null,
239 'groupOrder' => $group ? (int) $group['order'] : null,
240 );
241
242 /**
243 * Filter the entity descriptor built for a single post type.
244 *
245 * Same contract as an entry in `openstation_my_wordpress_entities`
246 * — see that filter for the field list.
247 *
248 * **Status: Experimental**
249 *
250 * @param array $entity Entity descriptor.
251 * @param WP_Post_Type $post_type Post type object.
252 */
253 return (array) apply_filters( 'openstation_my_wordpress_post_type_entity', $entity, $post_type );
254 }
255
256 /**
257 * Append a section per eligible post type to the site window's entity
258 * list.
259 *
260 * @param array[] $entities Existing entity descriptors.
261 * @return array[] Entity descriptors with CPT sections appended.
262 */
263 function openstation_my_wordpress_append_post_type_entities( $entities ) {
264 if ( ! is_array( $entities ) ) {
265 return $entities;
266 }
267
268 $existing = array();
269 foreach ( $entities as $entity ) {
270 if ( ! empty( $entity['post_type'] ) ) {
271 $existing[ (string) $entity['post_type'] ] = true;
272 }
273 }
274
275 foreach ( openstation_my_wordpress_eligible_post_types() as $name => $post_type ) {
276 if ( isset( $existing[ $name ] ) ) {
277 continue;
278 }
279 $entities[] = openstation_my_wordpress_post_type_entity( $post_type );
280 }
281
282 return $entities;
283 }
284 add_filter( 'openstation_my_wordpress_entities', 'openstation_my_wordpress_append_post_type_entities' );
285