forked from alleyinteractive/wordpress-fieldmanager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fieldmanager.php
667 lines (603 loc) · 23.6 KB
/
fieldmanager.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
<?php
/**
* Fieldmanager Base Plugin File.
*
* @package Fieldmanager
* @version 1.4.0
*/
/*
Plugin Name: Fieldmanager
Plugin URI: https://github.com/alleyinteractive/wordpress-fieldmanager
Description: Add fields to WordPress programmatically.
Author: Alley
Version: 1.4.0
Author URI: https://www.alley.co/
*/
/**
* Current version of Fieldmanager.
*/
define( 'FM_VERSION', '1.4.0' );
/**
* Filesystem path to Fieldmanager.
*/
define( 'FM_BASE_DIR', dirname( __FILE__ ) );
/**
* Default version number for static assets registered via Fieldmanager.
*/
define( 'FM_GLOBAL_ASSET_VERSION', 1 );
/**
* Whether to display debugging information. Default is value of WP_DEBUG.
*/
if ( ! defined( 'FM_DEBUG' ) ) {
define( 'FM_DEBUG', WP_DEBUG );
}
/**
* Load a Fieldmanager class based on a class name.
*
* Understands Fieldmanager nomenclature to find the right file within the
* plugin, but does not know how to load classes outside of that. If you build
* your own field, you will have to include it or autoload it for yourself.
*
* @see fieldmanager_load_file() for more detail about possible return values.
*
* @param string $class Class name to load.
* @return mixed The result of fieldmanager_load_file() or void if the class is
* not found.
*/
function fieldmanager_load_class( $class ) {
if ( class_exists( $class ) || 0 !== strpos( $class, 'Fieldmanager' ) ) {
return;
}
$class_id = strtolower( substr( $class, strrpos( $class, '_' ) + 1 ) );
if ( 0 === strpos( $class, 'Fieldmanager_Context' ) ) {
if ( 'context' === $class_id ) {
return fieldmanager_load_file( 'context/class-fieldmanager-context.php' );
}
return fieldmanager_load_file( 'context/class-fieldmanager-context-' . $class_id . '.php' );
}
if ( 0 === strpos( $class, 'Fieldmanager_Content' ) ) {
if ( 'content' === $class_id ) {
return fieldmanager_load_file( 'content/class-fieldmanager-content.php' );
}
return fieldmanager_load_file( 'content/class-fieldmanager-content-' . $class_id . '.php' );
}
if ( 0 === strpos( $class, 'Fieldmanager_Datasource' ) ) {
if ( 'datasource' === $class_id ) {
return fieldmanager_load_file( 'datasource/class-fieldmanager-datasource.php' );
}
return fieldmanager_load_file( 'datasource/class-fieldmanager-datasource-' . $class_id . '.php' );
}
if ( 0 === strpos( $class, 'Fieldmanager_Util' ) ) {
return fieldmanager_load_file( 'util/class-fieldmanager-util-' . $class_id . '.php' );
}
if ( 0 === strpos( $class, 'Fieldmanager\Libraries' ) ) {
// Convert namespace to array.
$classes = explode( '\\', $class );
// Drop the `Fieldmanager` namespace.
array_shift( $classes );
// Lowercase the namespace.
$classes = array_map( 'strtolower', $classes );
// Pop the last part to use as the filename.
$file_name = array_pop( $classes );
return fieldmanager_load_file( implode( '/', $classes ) . '/class-' . $file_name . '.php' );
}
return fieldmanager_load_file( 'class-fieldmanager-' . $class_id . '.php' );
}
if ( function_exists( 'spl_autoload_register' ) ) {
spl_autoload_register( 'fieldmanager_load_class' );
}
/**
* Load a Fieldmanager file.
*
* @throws FM_Class_Not_Found_Exception The Class is not found.
*
* @param string $file File to load.
*/
function fieldmanager_load_file( $file ) {
$file = FM_BASE_DIR . '/php/' . $file;
if ( ! file_exists( $file ) ) {
throw new FM_Class_Not_Found_Exception( $file );
}
// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- baseline
require_once $file;
}
// Load utility classes with helper functions. These can't be autoloaded.
fieldmanager_load_file( 'util/class-fieldmanager-util-term-meta.php' );
fieldmanager_load_file( 'util/class-fieldmanager-util-validation.php' );
/**
* Tell Fieldmanager that it has a base URL somewhere other than the plugins URL.
*
* @param string $path The URL to Fieldmanager, excluding "fieldmanager/", but
* including a trailing slash.
*/
function fieldmanager_set_baseurl( $path ) {
_fieldmanager_registry( 'baseurl', trailingslashit( $path ) );
}
/**
* Get the Fieldmanager base URL.
*
* @return string The URL pointing to the top Fieldmanager.
*/
function fieldmanager_get_baseurl() {
$path_override = _fieldmanager_registry( 'baseurl' );
if ( $path_override ) {
return $path_override;
}
return plugin_dir_url( __FILE__ );
}
/**
* Get the path to a field template.
*
* @param string $tpl_slug The name of a template file inside the "templates/"
* directory, excluding ".php".
* @return string The template path, or the path to "textfield.php" if the
* requested template is not found.
*/
function fieldmanager_get_template( $tpl_slug ) {
if ( ! file_exists( plugin_dir_path( __FILE__ ) . 'templates/' . $tpl_slug . '.php' ) ) {
$tpl_slug = 'textfield';
}
return plugin_dir_path( __FILE__ ) . 'templates/' . $tpl_slug . '.php';
}
/**
* Enqueue a script, optionally localizing data to it.
*
* @see Fieldmanager_Util_Assets::add_script().
*
* @param string $handle Script handle.
* @param string|bool $path Optional. The path to the file inside $plugin_dir.
* Default false.
* @param array $deps Optional. Script dependencies. Default empty array.
* @param string|bool $ver Optional. Script version. Default none.
* @param bool $in_footer Optional. Whether to render the script in the footer.
* Default false.
* @param string $data_object Optional. The $object_name in wp_localize_script().
* Default none.
* @param array $data Optional. The $l10n in wp_localize_script(). Default empty
* array.
* @param string $plugin_dir The base URL to the directory with the script.
* Default none.
* @param bool $admin Deprecated.
*/
function fm_add_script( $handle, $path = false, $deps = array(), $ver = false, $in_footer = false, $data_object = '', $data = array(), $plugin_dir = '', $admin = true ) {
// Ensure the Fieldmanager loader has been enqueued.
Fieldmanager_Util_Assets::instance()->add_script(
array(
'handle' => 'fm_loader',
'path' => 'js/fieldmanager-loader.js',
'deps' => array( 'jquery' ),
'ver' => FM_VERSION,
)
);
// Enqueue the requested script.
Fieldmanager_Util_Assets::instance()->add_script( compact( 'handle', 'path', 'deps', 'ver', 'in_footer', 'data_object', 'data', 'plugin_dir' ) );
}
/**
* Register and enqueue a style.
*
* @see Fieldmanager_Util_Assets::add_style().
*
* @param string $handle Stylesheet name.
* @param string|bool $path Optional. Path to the file inside of the Fieldmanager
* base URL. Default false.
* @param array $deps Optional. Stylesheet dependencies. Default empty array.
* @param string|bool $ver Optional. Stylesheet version. Default none.
* @param string $media Optional. Media for this stylesheet. Default 'all'.
* @param bool $admin Deprecated.
*/
function fm_add_style( $handle, $path = false, $deps = array(), $ver = false, $media = 'all', $admin = true ) {
Fieldmanager_Util_Assets::instance()->add_style( compact( 'handle', 'path', 'deps', 'ver', 'media' ) );
}
/**
* Get or set values from a simple, static registry.
*
* Keeps the globals out.
*
* @param string $var The variable name to set.
* @param mixed $val The value to store for $var. Default null.
* @return mixed The stored value of $var if $val is null, or false if $val is
* null and $var was not set in the registry, or void if $val is
* being set.
*/
function _fieldmanager_registry( $var, $val = null ) {
static $registry;
if ( ! is_array( $registry ) ) {
$registry = array();
}
if ( null === $val ) {
return isset( $registry[ $var ] ) ? $registry[ $var ] : false;
}
$registry[ $var ] = $val;
}
/**
* Get the context for triggers and pattern matching.
*
* This function is crucial for performance. It prevents the unnecessary
* initialization of FM classes, and the unnecessary loading of CSS and
* JavaScript.
*
* @see fm_calculate_context() for detail about the returned array values.
*
* @param bool $recalculate Optional. If true, FM will recalculate the current
* context. This is necessary for testing and perhaps
* other programmatic purposes.
* @return array Contextual information for the current request.
*/
function fm_get_context( $recalculate = false ) {
static $calculated_context;
if ( ! $recalculate && $calculated_context ) {
return $calculated_context;
} else {
$calculated_context = fm_calculate_context();
return $calculated_context;
}
}
/**
* Calculate contextual information for the current request.
*
* You can't use this function to determine whether or not a context "form" will
* be displayed, since it can be used anywhere. We would love to use
* get_current_screen(), but it's not available in some POST actions, and
* generally not available early enough in the init process.
*
* This is a function to watch closely as WordPress changes, since it relies on
* paths and variables.
*
* @return array {
* Array of context information.
*
* @type string|null A Fieldmanager context of "post", "quickedit", "term",
* "submenu", or "user", or null if one isn't found.
* @type string|null A "type" dependent on the context. For "post" and
* "quickedit", the post type. For "term", the taxonomy.
* For "submenu", the group name. For all others, null.
* }
*/
function fm_calculate_context() {
$calculated_context = array( null, null );
// Safe to use at any point in the load process, and better than URL matching.
if ( is_admin() && ! empty( $_SERVER['PHP_SELF'] ) ) {
$php_self = sanitize_text_field( wp_unslash( $_SERVER['PHP_SELF'] ) );
$script = substr( $php_self, strrpos( $php_self, '/' ) + 1 );
$get_post_type = '';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- baseline
if ( ! empty( $_GET['post_type'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- baseline
$get_post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
}
$get_page = '';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- baseline
if ( ! empty( $_GET['page'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- baseline
$get_page = sanitize_text_field( wp_unslash( $_GET['page'] ) );
}
$post_action = '';
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
if ( ! empty( $_POST['action'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
$post_action = sanitize_text_field( wp_unslash( $_POST['action'] ) );
}
/*
* Calculate a submenu context.
*
* For submenus of the default WordPress menus, the submenu's parent
* slug should match the requested script. For submenus of custom menu
* pages, where "admin.php" is the requested script but not the parent
* slug, the submenu's slug should match the GET request.
*
* @see fm_register_submenu_page() for detail about $submenu array values.
*/
if ( ! empty( $get_page ) ) {
$submenus = _fieldmanager_registry( 'submenus' );
if ( ! empty( $get_post_type ) ) {
if ( post_type_exists( $get_post_type ) ) {
$script .= "?post_type={$get_post_type}";
}
}
if ( $submenus ) {
foreach ( $submenus as $submenu ) {
if ( $script === $submenu[0] || ( 'admin.php' === $script && $get_page === $submenu[4] ) ) {
$calculated_context = array( 'submenu', $get_page );
}
}
}
}
if ( empty( $calculated_context[0] ) ) {
switch ( $script ) {
// Context = "post".
case 'post.php':
if ( ! empty( $post_action ) && ( 'editpost' === $post_action || 'newpost' === $post_action ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- baseline
$calculated_context = array( 'post', sanitize_text_field( wp_unslash( $_POST['post_type'] ) ) );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- baseline
} elseif ( ! empty( $_GET['post'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- baseline
$calculated_context = array( 'post', get_post_type( intval( $_GET['post'] ) ) );
}
break;
case 'post-new.php':
$calculated_context = array( 'post', ! empty( $get_post_type ) ? $get_post_type : 'post' );
break;
// Context = "user".
case 'profile.php':
case 'user-edit.php':
$calculated_context = array( 'user', null );
break;
// Context = "quickedit".
case 'edit.php':
$calculated_context = array( 'quickedit', ! empty( $get_post_type ) ? $get_post_type : 'post' );
break;
case 'admin-ajax.php':
// Passed in via an Ajax form.
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
if ( ! empty( $_POST['fm_context'] ) ) {
// phpcs:ignore Generic.Formatting.MultipleStatementAlignment.NotSameWarning, WordPress.Security.NonceVerification.Missing -- baseline
$subcontext = ! empty( $_POST['fm_subcontext'] ) ? sanitize_text_field( wp_unslash( $_POST['fm_subcontext'] ) ) : null;
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
$calculated_context = array( sanitize_text_field( wp_unslash( $_POST['fm_context'] ) ), $subcontext );
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
} elseif ( ! empty( $_POST['screen'] ) && ! empty( $post_action ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
if ( 'edit-post' === $_POST['screen'] && 'inline-save' === $post_action ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- baseline
$calculated_context = array( 'quickedit', sanitize_text_field( wp_unslash( $_POST['post_type'] ) ) );
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
} elseif ( 'add-tag' === $post_action && ! empty( $_POST['taxonomy'] ) ) {
// Context = "term".
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
$calculated_context = array( 'term', sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) ) );
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- baseline
} elseif ( ! empty( $_GET['action'] ) && 'fm_quickedit_render' === $_GET['action'] ) {
// Context = "quickedit".
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- baseline
$calculated_context = array( 'quickedit', sanitize_text_field( wp_unslash( $_GET['post_type'] ) ) );
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
} elseif ( isset( $_POST['action'] ) && 'heartbeat' === $_POST['action'] && isset( $_POST['data']['fm_context'] ) ) {
$calculated_context = array(
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
sanitize_text_field( wp_unslash( $_POST['data']['fm_context'] ) ),
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
isset( $_POST['data']['fm_subcontext'] ) ? sanitize_text_field( wp_unslash( $_POST['data']['fm_subcontext'] ) ) : null,
);
}
break;
// Context = "term".
case 'edit-tags.php':
case 'term.php':
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
if ( ! empty( $_POST['taxonomy'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- baseline
$calculated_context = array( 'term', sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) ) );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- baseline
} elseif ( ! empty( $_GET['taxonomy'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- baseline
$calculated_context = array( 'term', sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) ) );
}
break;
// Context = "nav-menu".
case 'nav-menus.php':
$calculated_context = array( 'nav_menu', null );
break;
}
}
}
/**
* Filters the Fieldmanager context calculated for the current request.
*
* @param array $calculated_context Array of context and 'type' information.
*/
return apply_filters( 'fm_calculated_context', $calculated_context );
}
/**
* Check whether a context is active.
*
* @see fm_get_context() for detail about the array values this function tries
* to match.
*
* @param string $context The Fieldmanager context to check for.
* @param string|array $type Type or types to check for. Default null.
* @return bool True if $context is "form". If $type is null, true if $context
* matches the first value of fm_get_context(). If $type is a
* string or array, true if the second value of fm_get_context()
* matches the string or is in the array and the first value
* matches $context. False otherwise.
*/
function fm_match_context( $context, $type = null ) {
if ( 'form' === $context ) {
// Nothing to check, since forms can be anywhere.
return true;
}
$calculated_context = fm_get_context();
if ( $calculated_context[0] === $context ) {
if ( null !== $type ) {
if ( is_array( $type ) ) {
return in_array( $calculated_context[1], $type, true );
}
return ( $calculated_context[1] === $type );
}
return true;
}
return false;
}
/**
* Fire an action for the current Fieldmanager context, if it exists.
*
* @see fm_calculate_context() for detail about the values that determine the
* name of the action. Two actions are defined, but only one at most fires.
*/
function fm_trigger_context_action() {
$calculated_context = fm_get_context();
if ( empty( $calculated_context[0] ) ) {
return;
}
list( $context, $type ) = $calculated_context;
if ( $type ) {
/**
* Fires when a specific Fieldmanager context and type load.
*
* The dynamic portions of the hook name, $context and $type, refer to
* the values returned by fm_calculate_context(). For example, the Edit
* screen for the Page post type would fire "fm_post_page".
*
* @param string $type The context subtype, e.g. the post type, taxonomy
* name, submenu option name.
*/
do_action( "fm_{$context}_{$type}", $type );
}
/**
* Fires when any Fieldmanager context loads.
*
* The dynamic portion of the hook name, $context, refers to the first
* value returned by fm_calculate_context(). For example, the Edit User
* screen would fire "fm_user".
*
* @param string|null $type The context subtype, e.g. the post type,
* taxonomy name, submenu option name. null if this
* context does not have a subtype.
*/
do_action( "fm_{$context}", $type );
}
add_action( 'init', 'fm_trigger_context_action', 99 );
/**
* Add the Fieldmanager context to the global FM JavaScript object.
*
* @since 1.3.0
*
* @param array $scripts Arrays of script arguments.
* @return array Updated arrays.
*/
function fm_localize_context( $scripts ) {
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- baseline
$index = array_search( 'fieldmanager_script', wp_list_pluck( $scripts, 'handle' ) );
if ( false === $index ) {
return $scripts;
}
list(
$scripts[ $index ]['data']['context']['context'],
$scripts[ $index ]['data']['context']['type']
) = fm_calculate_context();
return $scripts;
}
add_filter( 'fm_enqueue_scripts', 'fm_localize_context' );
/**
* Add data about a submenu page to the Fieldmanager registry under a slug.
*
* @see Fieldmanager_Context_Submenu for detail about $parent_slug, $page_title,
* $menu_title, $capability, and $menu_slug.
*
* @throws FM_Duplicate_Submenu_Name_Exception The Duplicate submenu name exception.
*
* @param string $group_name A slug to register the submenu page under.
* @param string $parent_slug Parent menu slug name or admin page file name.
* @param string $page_title Page title.
* @param string $menu_title Menu title. Falls back to $page_title if not set. Default null.
* @param string $capability Capability required to access the page. Default "manage_options".
* @param string $menu_slug Unique slug name for this submenu. Falls back to
* $group_name if not set. Default null.
*/
function fm_register_submenu_page( $group_name, $parent_slug, $page_title, $menu_title = null, $capability = 'manage_options', $menu_slug = null ) {
$submenus = _fieldmanager_registry( 'submenus' );
if ( ! $submenus ) {
$submenus = array();
}
if ( isset( $submenus[ $group_name ] ) ) {
/* translators: %s: group name */
throw new FM_Duplicate_Submenu_Name_Exception( sprintf( esc_html__( '%s is already in use as a submenu name', 'fieldmanager' ), $group_name ) );
}
if ( ! $menu_title ) {
$menu_title = $page_title;
}
/**
* These data will be used to add a Fieldmanager_Context_Submenu instance to
* the Fieldmanager registry if this submenu page is active.
*
* @see Fieldmanager_Field::activate_submenu_page().
*/
$submenus[ $group_name ] = array( $parent_slug, $page_title, $menu_title, $capability, $menu_slug ?: $group_name, '_fm_submenu_render' );
_fieldmanager_registry( 'submenus', $submenus );
}
/**
* Render a submenu page registered through the Fieldmanager registry.
*
* @see _fm_add_submenus().
*
* @throws FM_Submenu_Not_Initialized_Exception The submenu is not initialized exception.
*/
function _fm_submenu_render() {
$context = _fieldmanager_registry( 'active_submenu' );
if ( ! is_object( $context ) ) {
throw new FM_Submenu_Not_Initialized_Exception( esc_html__( 'The Fieldmanger context for this submenu was not initialized', 'fieldmanager' ) );
}
$context->render_submenu_page();
}
/**
* Register submenu pages from the Fieldmanager registry.
*/
function _fm_add_submenus() {
$submenus = _fieldmanager_registry( 'submenus' );
if ( ! is_array( $submenus ) ) {
return;
}
foreach ( $submenus as $s ) {
call_user_func_array( 'add_submenu_page', $s );
}
}
add_action( 'admin_menu', '_fm_add_submenus', 15 );
/**
* Sanitize multi-line text.
*
* @param string $value Unsanitized text.
* @return string Text with each line of $value passed through sanitize_text_field().
*/
function fm_sanitize_textarea( $value ) {
return implode( "\n", array_map( 'sanitize_text_field', explode( "\n", $value ) ) );
}
/**
* Stripslashes_deep for submenu data.
*/
add_filter( 'fm_submenu_presave_data', 'stripslashes_deep' );
/**
* Exception class for Fieldmanager's fatal errors.
*
* Used mostly to differentiate in unit tests.
*
* @package Fieldmanager
*/
class FM_Exception extends Exception { }
/**
* Exception class for classes that could not be loaded.
*
* @package Fieldmanager
*/
class FM_Class_Not_Found_Exception extends Exception { }
/**
* Exception class for unitialized submenus.
*
* @package Fieldmanager
*/
class FM_Submenu_Not_Initialized_Exception extends Exception { }
/**
* Exception class for duplicate submenu names.
*
* @package Fieldmanager
*/
class FM_Duplicate_Submenu_Name_Exception extends Exception { }
/**
* Exception class for Fieldmanager's developer errors.
*
* Used mostly to differentiate in unit tests. This exception is meant to help
* developers write correct Fieldmanager implementations.
*
* @package Fieldmanager
*/
class FM_Developer_Exception extends Exception { }
/**
* Exception class for Fieldmanager's validation errors.
*
* Used mostly to differentiate in unit tests. Validation errors in WordPress
* are not really recoverable.
*
* @package Fieldmanager
*/
class FM_Validation_Exception extends Exception { }