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