WordPress.org

Make WordPress Core

Ticket #18179: meta-box.4.php

File meta-box.4.php, 11.0 KB (added by ericlewis, 6 years ago)

add missing check for nonce existence before validating.

Line 
1<?php
2/*
3Plugin Name: Custom Metaboxes for Core
4*/
5
6class WP_Meta_Box {
7        /**
8         * The screen where the meta box will be shown.
9         */
10        public $screen;
11
12        /**
13         * A unique slug to allow multiple meta boxes of the same class
14         * to exist on the same screen.
15         */
16        public $slug;
17
18        /**
19         * A unique identifier for the meta box.
20         *
21         * A string composed from the meta box class, screen, and slug.
22         */
23        public $unique_id;
24
25        /**
26         * Arguments passed to the meta box constructor.
27         */
28        public $args;
29
30        /**
31         * A registry that tracks each meta box instance.
32         */
33        public static $registry;
34
35        /**
36         * A multi-dimensional array of registered meta fields, and their settings.
37         */
38        public $meta_fields;
39
40
41        /**
42         * Constructor.
43         *
44         * Any subclasses MUST call parent::_construct( $screen, $args ).
45         *
46         * @param string $screen - The ID (string) for the screen where the meta box will be shown.
47         * @param array  $args - An array of supplied arguments. Optional.
48         *
49         * $args recognized by default:
50         *    title    - The title of the meta box.
51         *    context  - The context within the page where the box should show ('normal', 'advanced').
52         *    priority - The priority within the context where the boxes should show ('high', 'low').
53         *    slug     - A unique string to identify the meta box.
54         */
55        public function __construct( $screen, $args = array() ) {
56                // Set variables
57                $this->screen = $screen;
58                $this->slug   = empty( $args['slug'] ) ? '' : $args['slug'];
59
60                // Remove slug from args
61                unset( $args['slug'] );
62
63                // Set default args
64                $defaults = array(
65                        'title'         => __( 'Untitled' , 'wordcampbase'),
66                        'context'       => 'side',
67                        'priority'      => 'default',
68                );
69                $this->args = wp_parse_args( $args, $defaults );
70
71
72                add_action( 'save_post', array( $this, '_save' ), 10, 99 ); // Effectively infinite args.
73
74                // Add the meta box to the registry.
75                // Generates a slug if one doesn't already exist.
76                WP_Meta_Box::registry()->add( $this );
77
78                // Construct the meta box's unique ID.
79                $this->unique_id = get_class( $this ) . "_{$this->screen}_{$this->slug}";
80
81                // Bind hooks
82                add_action( 'admin_init',            array( $this, '_register_meta_box' ) );
83                add_action( 'admin_enqueue_scripts', array( $this, '_enqueue_scripts' ) );
84        }
85
86        /**
87         * Fetches the meta box registry.
88         */
89        public static function registry() {
90                if ( ! isset( WP_Meta_Box::$registry ) )
91                        WP_Meta_Box::$registry = new WP_Meta_Box_Registry();
92                return WP_Meta_Box::$registry;
93        }
94
95        /**
96         * Fires when the meta box is registered. Override in a subclass.
97         */
98        protected function register() {}
99
100        /**
101         * Remove the meta box.
102         */
103        public function remove() {
104                // Make sure we've removed any data from the registry.
105                WP_Meta_Box::registry()->remove( get_class( $this ), $this->screen, $this->slug );
106
107                // Unbind actions.
108                remove_action( 'admin_init',            array( $this, '_register_meta_box' ) );
109                remove_action( 'admin_enqueue_scripts', array( $this, '_enqueue_scripts' ) );
110        }
111       
112        /**
113         * Register a post meta field.
114         *
115         * For use in a subclass.
116         */
117        protected function register_meta_field( $meta_key, $args = array() ) {
118
119                $defaults = array( 
120                        'type' => 'text',
121                        'render_callback' => array( $this, 'render_field' ),
122                        'sanitize_callback' => array( $this, 'sanitize' ),
123                        'auth_callback' => array( $this, 'authorize' ),
124                        'title_label' => $meta_key,
125                        'data' => array(), // not yet used. to preload form fields that expect data (radios, selects, etc.)
126                );
127
128                $args = wp_parse_args( $args, $defaults );
129
130                extract( $args );
131
132                // Register meta with WordPress. Sets up sanitization and authorization callbacks.
133                register_meta( 'post', $meta_key, $sanitize_callback, $auth_callback );
134
135                // Store meta field details in an array for later.
136                $this->meta_fields[] = array( 
137                        'type' => $type, 
138                        'meta_key' => $meta_key, 
139                        'render_callback' => $render_callback,
140                        'title_label' => $title_label, 
141                        'data' => array(),
142                );
143        }
144
145        /**
146         * Render the meta box.
147         */
148        public function render_meta_box() {
149                foreach ( $this->meta_fields as $meta_field ) {
150                        call_user_func( $meta_field['render_callback'], $meta_field );
151                }
152        }
153
154        /**
155         * Render callback for generic meta fields.
156         */
157        protected function render_field( $meta_field ) {
158                global $post;
159               
160                extract( $meta_field );
161                $meta_value = get_post_meta( $post->ID, $meta_key, true );
162                echo "<h4>$title_label</h4>";
163                switch ( $type ) {
164                        case 'text':
165                                echo '<input type="text" id="' . $meta_key . '" name="meta-field-' . $meta_key . '" value="' . esc_attr( $meta_value ) .'">';
166                        break;
167                        case 'wp_editor':
168                                wp_editor( $meta_value, 'meta-field-' . $meta_key );
169                        break;
170                }
171        }
172
173        /**
174         * Enqueue meta box scripts. Override in a subclass.
175         */
176        protected function enqueue_scripts() {
177                $unique_meta_types = array_unique( wp_list_pluck( $this->meta_fields, 'type' ) );
178                foreach ( $unique_meta_types as $meta_type ) {
179                        switch ( $meta_type ) {
180                                // Specific script enqueues for the displayed meta fields.
181                        }
182                }
183        }
184
185        /**
186         * Sanitization callback for generic meta fields.
187         */
188        public function sanitize( $meta_value, $meta_key ) {
189                foreach ( $this->meta_fields as $meta_field ) {
190                        if ( $meta_field['meta_key'] == $meta_key ) {
191                                switch ( $meta_field['type'] ) {
192                                        case 'text' :
193                                                $meta_value = sanitize_text_field( $meta_value );
194                                        break;
195                                        case 'wp_editor' :
196                                                $meta_value = wp_kses_post( $meta_value );
197                                        break;
198                                }
199                        }
200                }
201                return $meta_value;
202        }
203
204        /**
205         * Default authorization callback.
206         */
207        public function authorize( $allowed, $meta_key, $post_ID, $user_id, $cap, $caps ) {
208                return true;
209        }
210
211        /**
212         * Save the meta fields' data.
213         */
214        public function save() {
215                global $post;
216                foreach ( $this->meta_fields as $meta_field ) {
217                        // Capabilities check.
218                        if ( current_user_can( 'edit_post_meta', $post->ID, $meta_field['meta_key'] ) ) {
219                                // Check if the $_REQUEST array has the specific key set.
220
221                                if ( isset( $_REQUEST['meta-field-' . $meta_field['meta_key']] ) ) {
222                                        $meta_value = $_REQUEST['meta-field-' . $meta_field['meta_key']];
223                                        update_post_meta( $post->ID, $meta_field['meta_key'], $meta_value );
224                                }
225                        }
226                }
227        }
228
229        /**
230         * Determine whether the meta box will be saved. Override in a subclass if necessary.
231         *
232         * Return true to save, false to cancel.
233         * @param int    The post ID.
234         * @param $post  The post object.
235         */
236        protected function maybe_save( $post_id, $post ) {
237                // Bail if we're autosaving
238                if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
239                        return;
240
241                // @TODO Add revision check
242
243                // Cap check
244                if ( ! current_user_can( 'edit_post', $post_id ) )
245                        die;
246
247                return true;
248        }
249
250        /* =====================================================================
251         * INTERNAL FUNCTIONS
252         * ===================================================================== */
253
254        /**
255         * Internal function. Registers the meta box.
256         */
257        public final function _register_meta_box() {
258                $id = "{$this->unique_id}-meta-box";
259
260                add_meta_box( $id, $this->args['title'], array( $this, '_render' ),
261                        $this->screen, $this->args['context'], $this->args['priority'],
262                        $this->args );
263
264                $this->register();
265        }
266
267        /**
268         * Internal function. Ensures scripts are only loaded when necessary.
269         */
270        public final function _enqueue_scripts() {
271                $current_screen = get_current_screen();
272
273                if ( isset( $current_screen ) && $this->screen == $current_screen->id )
274                        $this->enqueue_scripts();
275        }
276
277        /**
278         * Internal function, initiates the rendering process.
279         */
280        public final function _render() { // todo should this be public? can subclasses access public parent class functions?
281                wp_nonce_field( "{$this->unique_id}_nonce", "_wpnonce_{$this->unique_id}", false );
282               
283                $this->render_meta_box();
284        }
285
286        /**
287         * Internal function, initiates the saving process.
288         */
289        public final function _save() {
290                $current_screen = get_current_screen();
291
292                if ( ! isset( $current_screen ) || $this->screen != $current_screen->id || ! isset( $_REQUEST['"_wpnonce_{$this->unique_id}"'] ) )
293                        return;
294                // Nonce check (sorry, you don't have a choice about this one).
295                check_admin_referer( "{$this->unique_id}_nonce", "_wpnonce_{$this->unique_id}" );
296
297                $args = func_get_args();
298                // Check if we're autosaving/capabilities check.
299                if ( call_user_func_array( array( $this, 'maybe_save' ), $args ) ) {
300                        // Save the meta fields' data.
301                        call_user_func_array( array( $this, 'save' ), $args );
302                }
303        }
304}
305
306
307
308/**
309 * Meta Box Registry
310 *
311 * Implemented as singleton
312 *
313 */
314class WP_Meta_Box_Registry {
315
316        /**
317         * WP_Meta_Box_Registry singleton implementation
318         *
319         * @var WP_Meta_Box_Registry
320         */
321        private static $instance;
322
323        private $instances = array();
324
325        /**
326         * WP_Meta_Box_Registry singleton implementation
327         *
328         * @return WP_Meta_Box_Registry
329         */
330        public static function instance() {
331                if (null === self::$instance)
332                        self::$instance = new self();
333                return self::$instance;
334        }
335
336        /**
337         * Search registry for meta box instances
338         *
339         * @return array result
340         */
341        public function search($class_or_object, $screen = false, $slug = false) {
342                $instances = array();
343
344                foreach($this->instances as $instance) {
345
346                        if ( ! $instance instanceof $class_or_object )
347                                continue;
348
349                        if ( $screen !== false && $instance->screen !== $screen )
350                                continue;
351
352                        if ( $slug !== false && $instance->slug !== $slug )
353                                continue;
354
355                        $instances[] = $instance;
356                }
357
358                return $instances;
359        }
360
361        /**
362         * Adds a meta box instance.
363         *
364         * If $instance->slug is defined, will use $slug.
365         * If a meta box with the same slug exists, it will be overwritten.
366         *
367         * @param WP_Meta_Box $instance
368         */
369        public function add( WP_Meta_Box $instance ) {
370                static $counter = 0;
371
372                $hash = spl_object_hash( $instance );
373
374                if ( !empty( $instance->slug ) ) {
375                        // If slug is specified, remove existing instance
376                        $instances = $this->find( $instance, $instance->screen, $instance->slug );
377
378                        if ($instances) 
379                                $this->remove($instances[0]);
380
381                } else {
382                        // If no slug is specified, get the numerical index.
383                        // alternatively get the number of this instances' class by screen and count plus one.
384                        $instance->slug = ++$counter;
385                }
386
387                $this->instances[ $hash ] = $instance;
388        }
389
390        // in case this is still needed.
391        public function get( WP_Meta_Box $instance ) {
392                $hash = spl_object_hash( $instance );
393
394                if ( isset( $this->instances[ $hash ] ) )
395                        return $instance;
396
397                return false;
398        }
399
400        /**
401         * Remove instance from registry
402         *
403         * @param WP_Meta_Box $instance
404         */
405        public function remove( WP_Meta_Box $instance ) {
406                $hash = spl_object_hash( $instance );
407
408                if ( isset( $this->instances[ $hash ] ) )
409                        unset( $this->instances[ $hash ] );
410        }
411}
412
413class WP_Basic_Meta_Box extends WP_Meta_Box {
414        function __construct( $screen, $args = array() ) {
415                // Call the WP_Meta_Box constructor.
416                parent::__construct( $screen, $args );
417
418                // Save the meta box contents when 'save_post' is triggered.
419                $this->register_meta_field(
420                        '_basic_meta_box_test_field', 
421                        array( 
422                                'type' => 'text',
423                                'title_label' => 'Some Title here' 
424                        )
425                );
426                $this->register_meta_field(
427                        '_basic_meta_box_wp_editor_field', 
428                        array( 
429                                'type' => 'wp_editor',
430                                'title_label' => 'WP Editor!' 
431                        )
432                );
433        }
434}
435
436new WP_Basic_Meta_Box( 'post', array( 'title' => 'Basic Meta Box' ) );