PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.4.0-beta2
Secure Custom Fields v6.4.0-beta2
6.9.5 6.9.4 6.9.3 6.9.2 6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / pro / post-types / acf-ui-options-page.php
secure-custom-fields / pro / post-types Last commit date
acf-ui-options-page.php 1 year ago index.php 1 year ago
acf-ui-options-page.php
443 lines
1 <?php
2
3 if ( ! class_exists( 'ACF_UI_Options_Page' ) ) {
4
5 class ACF_UI_Options_Page extends ACF_Internal_Post_Type {
6
7 /**
8 * The ACF internal post type name.
9 *
10 * @var string
11 */
12 public $post_type = 'acf-ui-options-page';
13
14 /**
15 * The prefix for the key used in the main post array.
16 *
17 * @var string
18 */
19 public $post_key_prefix = 'ui_options_page_';
20
21 /**
22 * The cache key for a singular post.
23 *
24 * @var string
25 */
26 public $cache_key = 'acf_get_ui_options_page_post:key:';
27
28 /**
29 * The cache key for a collection of posts.
30 *
31 * @var string
32 */
33 public $cache_key_plural = 'acf_get_ui_options_page_posts';
34
35 /**
36 * The hook name for a singular post.
37 *
38 * @var string
39 */
40 public $hook_name = 'ui_options_page';
41
42 /**
43 * The hook name for a collection of posts.
44 *
45 * @var string
46 */
47 public $hook_name_plural = 'ui_options_pages';
48
49 /**
50 * The name of the store used for the post type.
51 *
52 * @var string
53 */
54 public $store = 'ui-options-pages';
55
56 /**
57 * Constructs the class and any parent classes.
58 *
59 * @since 6.2
60 */
61 public function __construct() {
62 $this->register_post_type();
63
64 // Include admin classes in admin.
65 if ( is_admin() ) {
66 acf_include( 'includes/admin/admin-internal-post-type-list.php' );
67 acf_include( 'includes/admin/admin-internal-post-type.php' );
68 acf_include( 'pro/admin/post-types/admin-ui-options-page.php' );
69 acf_include( 'pro/admin/post-types/admin-ui-options-pages.php' );
70 }
71
72 $this->setup_local_json();
73
74 parent::__construct();
75
76 add_action( 'acf/init', array( $this, 'register_ui_options_pages' ), 6 );
77 add_action( 'acf/include_options_pages', array( $this, 'include_json_options_pages' ) );
78 }
79
80 /**
81 * Registers the acf-ui-options-page custom post type with WordPress.
82 *
83 * @since 6.2
84 */
85 public function register_post_type() {
86 $cap = acf_get_setting( 'capability' );
87
88 register_post_type(
89 'acf-ui-options-page',
90 array(
91 'labels' => array(
92 'name' => __( 'Options Pages', 'acf' ),
93 'singular_name' => __( 'Options Pages', 'acf' ),
94 'add_new' => __( 'Add New', 'acf' ),
95 'add_new_item' => __( 'Add New Options Page', 'acf' ),
96 'edit_item' => __( 'Edit Options Page', 'acf' ),
97 'new_item' => __( 'New Options Page', 'acf' ),
98 'view_item' => __( 'View Options Page', 'acf' ),
99 'search_items' => __( 'Search Options Pages', 'acf' ),
100 'not_found' => __( 'No Options Pages found', 'acf' ),
101 'not_found_in_trash' => __( 'No Options Pages found in Trash', 'acf' ),
102 ),
103 'public' => false,
104 'hierarchical' => true,
105 'show_ui' => true,
106 'show_in_menu' => false,
107 '_builtin' => false,
108 'capability_type' => 'post',
109 'capabilities' => array(
110 'edit_post' => $cap,
111 'delete_post' => $cap,
112 'edit_posts' => $cap,
113 'delete_posts' => $cap,
114 ),
115 'supports' => false,
116 'rewrite' => false,
117 'query_var' => false,
118 )
119 );
120 }
121
122 /**
123 * Register activated options pages.
124 *
125 * @since 6.2
126 */
127 public function register_ui_options_pages() {
128 $child_pages = array();
129
130 // Register parent pages first so that child pages can be registered properly.
131 foreach ( $this->get_posts( array( 'active' => true ) ) as $options_page ) {
132 $options_page = $this->get_options_page_args( $options_page );
133
134 if ( empty( $options_page['parent_slug'] ) || 'none' === $options_page['parent_slug'] ) {
135 $options_page['parent_slug'] = '';
136 acf_add_options_page( $options_page );
137 } else {
138 $child_pages[] = $options_page;
139 }
140 }
141
142 foreach ( $child_pages as $child_page ) {
143 acf_add_options_sub_page( $child_page );
144 }
145 }
146
147 /**
148 * Gets the default settings array for an ACF options page.
149 *
150 * @return array
151 */
152 public function get_settings_array() {
153 return array(
154 // ACF internal settings.
155 'ID' => 0,
156 'key' => '',
157 'title' => '',
158 'active' => true,
159 'menu_order' => 0,
160 // Basic settings.
161 'page_title' => '',
162 'menu_slug' => '',
163 'parent_slug' => '',
164 'advanced_configuration' => false,
165 // Visibility tab.
166 'icon_url' => '',
167 'menu_title' => '',
168 'position' => null,
169 'redirect' => false,
170 'description' => '',
171 'menu_icon' => array(),
172 // Labels tab.
173 'update_button' => __( 'Update', 'acf' ),
174 'updated_message' => __( 'Options Updated', 'acf' ),
175 // Permissions tab.
176 'capability' => 'edit_posts',
177 'data_storage' => 'options',
178 'post_id' => '',
179 'autoload' => false,
180 );
181 }
182
183 /**
184 * Validates options page values before allowing save from the global $_POST object.
185 * Errors are added to the form using acf_add_internal_post_type_validation_error().
186 *
187 * @since 6.2
188 *
189 * @return boolean validity status
190 */
191 public function ajax_validate_values() {
192 if ( empty( $_POST['acf_ui_options_page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere.
193 return false;
194 }
195
196 $to_validate = acf_sanitize_request_args( wp_unslash( $_POST['acf_ui_options_page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere.
197 $post_id = acf_request_arg( 'post_id' );
198 $valid = true;
199 $menu_slug = (string) $to_validate['menu_slug'];
200
201 if ( preg_match( '/^[a-z0-9_-]*$/', $menu_slug ) !== 1 ) {
202 $valid = false;
203 acf_add_internal_post_type_validation_error( 'menu_slug', __( 'The menu slug must only contain lower case alphanumeric characters, underscores or dashes.', 'acf' ) );
204 }
205
206 // Check for duplicate menu_slug.
207 $options_pages = acf_get_options_pages();
208 $options_pages = is_array( $options_pages ) ? $options_pages : array();
209 $duplicates = array_filter(
210 $options_pages,
211 function ( $options_page ) use ( $post_id, $menu_slug ) {
212 // Current post is not a duplicate.
213 if ( isset( $options_page['ID'] ) && (int) $post_id === (int) $options_page['ID'] ) {
214 return false;
215 }
216
217 // Menu slugs match, could be a duplicate.
218 if ( $menu_slug === $options_page['menu_slug'] ) {
219 // Unless the matching slug is a parent page redirecting to the child page.
220 if ( isset( $options_page['_menu_slug'] ) && $options_page['_menu_slug'] !== $menu_slug ) {
221 return false;
222 }
223
224 return true;
225 }
226
227 return false;
228 }
229 );
230
231 if ( ! empty( $duplicates ) ) {
232 $valid = false;
233 acf_add_internal_post_type_validation_error(
234 'menu_slug',
235 __( 'This Menu Slug is already in use by another ACF Options Page.', 'acf' ),
236 'acf-ui-options-page'
237 );
238 }
239
240 return apply_filters( "acf/{$this->hook_name}/ajax_validate_values", $valid, $_POST['acf_ui_options_page'] ); // phpcs:ignore WordPress.Security -- Raw input send to hook for validation.
241 }
242
243 /**
244 * Updates the settings for ACF UI options pages.
245 *
246 * @since 6.2
247 *
248 * @param array $post The ACF post to update.
249 * @return array
250 */
251 public function update_post( $post ) {
252 if ( isset( $post['parent_slug'] ) && 'none' !== $post['parent_slug'] ) {
253 $ui_options_pages = $this->get_posts();
254
255 foreach ( $ui_options_pages as $options_page ) {
256 if ( $options_page['menu_slug'] === $post['parent_slug'] ) {
257 $post['_parent'] = $options_page['ID'];
258 break;
259 }
260 }
261 }
262
263 return parent::update_post( $post );
264 }
265
266 /**
267 * Sets up the local JSON functionality for options pages.
268 *
269 * @since 6.2
270 *
271 * @param ACF_Local_JSON $local_json The ACF_Local_JSON object.
272 * @return void
273 */
274 public function setup_local_json() {
275 $local_json = acf_get_instance( 'ACF_Local_JSON' );
276
277 // Event listeners.
278 add_action( 'acf/update_ui_options_page', array( $local_json, 'update_internal_post_type' ) );
279 add_action( 'acf/untrash_ui_options_page', array( $local_json, 'update_internal_post_type' ) );
280 add_action( 'acf/trash_ui_options_page', array( $local_json, 'delete_internal_post_type' ) );
281 add_action( 'acf/delete_ui_options_page', array( $local_json, 'delete_internal_post_type' ) );
282 }
283
284 /**
285 * Includes all local JSON options pages.
286 *
287 * @since 6.1
288 */
289 public function include_json_options_pages() {
290 $local_json = acf_get_instance( 'ACF_Local_JSON' );
291
292 // Bail early if disabled.
293 if ( ! $local_json->is_enabled() ) {
294 return;
295 }
296
297 // Get load paths.
298 $files = $local_json->scan_files( 'acf-ui-options-page' );
299 foreach ( $files as $key => $file ) {
300 $json = json_decode( file_get_contents( $file ), true );
301 $json['local'] = 'json';
302 $json['local_file'] = $file;
303 acf_add_local_internal_post_type( $json, 'acf-ui-options-page' );
304 }
305 }
306
307 /**
308 * Returns a string that can be used to create an options page with PHP.
309 *
310 * @since 6.2
311 *
312 * @param array $post The main options page array.
313 * @return string
314 */
315 public function export_post_as_php( $post = array() ) {
316 $return = '';
317 if ( empty( $post ) ) {
318 return $return;
319 }
320
321 // Validate and prepare the post for export.
322 $post = $this->validate_post( $post );
323 $args = $this->get_options_page_args( $post );
324
325 unset( $args['ID'] );
326
327 $code = var_export( $args, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions -- Used for PHP export.
328
329 if ( ! $code ) {
330 return $return;
331 }
332
333 $code = $this->format_code_for_export( $code );
334 $return .= "acf_add_options_page( {$code} );\r\n";
335
336 return esc_textarea( $return );
337 }
338
339 /**
340 * This function returns whether the value was saved prior to the icon picker field or not.
341 *
342 * @since 6.3
343 *
344 * @param mixed $args The args for the icon field.
345 * @return boolean
346 */
347 public function value_was_saved_prior_to_icon_picker_field( $args ) {
348 if (
349 ! empty( $args['menu_icon'] ) &&
350 is_array( $args['menu_icon'] ) &&
351 ! empty( $args['menu_icon']['type'] ) &&
352 ! empty( $args['menu_icon']['value'] )
353 ) {
354 return false;
355 }
356
357 return true;
358 }
359
360 /**
361 * Parses ACF options page settings and returns an array of args
362 * to be handled by `acf_add_options_page()`.
363 *
364 * Omits settings that line up with the defaults to reduce the size
365 * of the array passed to `acf_add_options_page()`, which might be exported.
366 *
367 * @since 6.2
368 *
369 * @param array $post The main ACF options page settings array.
370 * @return array
371 */
372 public function get_options_page_args( $post ) {
373 $args = array();
374 $defaults = $this->get_settings_array();
375
376 // UI-specific params that don't need to be passed in.
377 $ui_specific = array(
378 'key',
379 'title',
380 'active',
381 'menu_order',
382 'advanced_configuration',
383 'data_storage',
384 );
385
386 foreach ( $post as $setting => $value ) {
387 // Don't pass in UI specific or unknown settings.
388 if ( in_array( $setting, $ui_specific, true ) || ! array_key_exists( $setting, $defaults ) ) {
389 continue;
390 }
391
392 // Convert types.
393 $default_type = gettype( $defaults[ $setting ] );
394 if ( 'boolean' === $default_type ) {
395 $value = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
396 }
397
398 // Escape HTML.
399 if ( in_array( $setting, array( 'page_title', 'menu_title' ), true ) ) {
400 $value = esc_html( $value );
401 }
402
403 // A `parent_slug` value of "none" is only used in the UI.
404 if ( 'parent_slug' === $setting && 'none' === $value ) {
405 continue;
406 }
407
408 // UI does not default redirect to child to true, but code does.
409 if ( 'redirect' === $setting && ! $value ) {
410 $args[ $setting ] = $value;
411 continue;
412 }
413
414 // Don't need to include if it's the same as a default.
415 if ( $value === $defaults[ $setting ] ) {
416 continue;
417 }
418
419 $args[ $setting ] = $value;
420 }
421
422 // Override the icon_url if the value was saved after the icon picker was added to ACF in 6.3.
423 if ( ! $this->value_was_saved_prior_to_icon_picker_field( $args ) ) {
424 if ( $args['menu_icon']['type'] === 'url' ) {
425 $args['icon_url'] = $args['menu_icon']['value'];
426 }
427 if ( $args['menu_icon']['type'] === 'media_library' ) {
428 $image_url = wp_get_attachment_image_url($args['menu_icon']['value']);
429 $args['icon_url'] = $image_url;
430 }
431 if ( $args['menu_icon']['type'] === 'dashicons' ) {
432 $args['icon_url'] = $args['menu_icon']['value'];
433 }
434 }
435
436 return apply_filters( 'acf/ui_options_page/registration_args', $args, $post );
437 }
438 }
439
440 }
441
442 acf_new_instance( 'ACF_UI_Options_Page' );
443