Skip to:
Content

BuddyPress.org

Ticket #4955: bp-group-extension.php

File bp-group-extension.php, 34.8 KB (added by boonebgorges, 13 years ago)
Line 
1<?php
2
3/**
4 * API for creating group extensions without having to hardcode the content into
5 * the theme.
6 *
7 * To implement, extend this class. In your constructor, pass an optional array
8 * of arguments to parent::init() to configure your widget. The config array
9 * supports the following values:
10 *   - 'slug' A unique identifier for your extension. This value will be used
11 *     to build URLs, so make it URL-safe
12 *   - 'name' A translatable name for your extension. This value is used to
13       populate the navigation tab, as well as the default titles for admin/
14       edit/create tabs.
15 *   - 'visibility' Set to 'public' (default) for your extension (the main tab
16 *     as well as the widget) to be available to anyone who can access the
17 *     group, 'private' otherwise.
18 *   - 'nav_item_position' An integer explaining where the nav item should
19 *     appear in the tab list
20 *   - 'enable_nav_item' Set to true for your extension's main tab to be
21 *     available to anyone who can access the group.
22 *   - 'nav_item_name' The translatable text you want to appear in the nav tab.
23 *     Defaults to the value of 'name'.
24 *   - 'display_hook' The WordPress action that the widget_display() method is
25 *     hooked to
26 *   - 'template_file' The template file that will be used to load the content
27 *     of your main extension tab. Defaults to 'groups/single/plugins.php'.
28 *   - 'screens' A multi-dimensional array, described below
29 *
30 * BP_Group_Extension uses the concept of "settings screens". There are three
31 * contexts for settings screens:
32 *   - 'create', which inserts a new step into the group creation process
33 *   - 'edit', which adds a tab for your extension into the Admin section of
34 *     a group
35 *   - 'admin', which adds a metabox to the Groups administration panel in the
36 *     WordPress Dashboard
37 * Each of these settings screens is populated by a pair of methods: one that
38 * creates the markup for the screen, and one that processes form data
39 * submitted from the screen. If your plugin needs screens in all three
40 * contexts, and if the markup and form processing logic will be the same in
41 * each case, you can define two methods to handle all of the screens:
42 *   function settings_screen() {}
43 *   function settings_screen_save() {}
44 * If one or more of your settings screen needs separate logic, you may define
45 * context-specific methods, for example:
46 *   function edit_screen() {}
47 *   function edit_screen_save() {}
48 * BP_Group_Extension will use the more specific methods if they are available.
49 *
50 * You can further customize the settings screens (tab names, etc) by passing
51 * an optional 'screens' parameter to the init array. The format is as follows:
52 *   'screens' => array(
53 *       'create' => array(
54 *           'slug' => 'foo',
55 *           'name' => 'Foo',
56 *           'position' => 55,
57 *           'screen_callback' => 'my_create_screen_callback',
58 *           'screen_save_callback' => 'my_create_screen_save_callback',
59 *       ),
60 *       'edit' => array( // ...
61 *   ),
62 * Only provide those arguments that you actually want to change from the
63 * default configuration. BP_Group_Extension will do the rest.
64 *
65 * Note that the 'edit' screen accepts an additional parameter: 'submit_text',
66 * which defines the text of the Submit button automatically added to the Edit
67 * screen of the extension (defaults to 'Save Changes'). Also, the 'admin'
68 * screen accepts two additional parameters: 'metabox_priority' and
69 * 'metabox_context'. See the docs for add_meta_box() for more details on these
70 * arguments.
71 *
72 * Prior to BuddyPress 1.7, group extension configurations were set slightly
73 * differently. The legacy method is still supported, though deprecated.
74 *
75 * @package BuddyPress
76 * @subpackage Groups
77 * @since BuddyPress (1.1)
78 */
79class BP_Group_Extension {
80
81        /**
82         * @var array Information about this extension's screens
83         * @since BuddyPress (1.8)
84         */
85        var $screens = array();
86
87        /**
88         * @var string The name of the extending class
89         * @since BuddyPress (1.8)
90         */
91        var $class_name;
92
93        /**
94         * @var object A ReflectionClass object of the current extension
95         * @since BuddyPress (1.8)
96         */
97        var $class_reflection;
98
99        /**
100         * @var array Parsed configuration paramaters for the extension
101         * @since BuddyPress (1.8)
102         */
103        var $params = array();
104
105        /**
106         * @var int The id of the current group
107         * @since BuddyPress (1.8)
108         */
109        var $group_id;
110
111        /**
112         * @var string The slug of the current extension
113         */
114        var $slug;
115
116        /**
117         * @var string The translatable name of the current extension
118         */
119        var $name;
120
121        /**
122         * @var string Whether the extension tab is visible. 'public'
123         *   or 'private'
124         */
125        var $visibility;
126
127        /**
128         * @var int The numeric position of the main nav item
129         */
130        var $nav_item_position;
131
132        /**
133         * @var bool Whether to show the nav item
134         */
135        var $enable_nav_item;
136
137        /**
138         * @var string The text of the nav item. Defaults to self::name
139         */
140        var $nav_item_name;
141
142        /**
143         * @var string The WP action that self::widget_display() is attached
144         *   to. Defaults to 'groups_custom_group_boxes'
145         */
146        var $display_hook;
147
148        /**
149         * @var string The template file used to load the plugin content.
150         *   Defaults to 'groups/single/plugins'
151         */
152        var $template_file;
153
154        /**
155         * @var bool Has the extension been initialized?
156         * @since BuddyPress (1.8)
157         */
158        protected $initialized = false;
159
160        /**
161         * @var array Extension properties as set by legacy extensions
162         * @since BuddyPress (1.8)
163         */
164        protected $legacy_properties = array();
165
166        /**
167         * @var array Extension properties as set by legacy extensions, but
168         *   converted to match the new format for params
169         * @since BuddyPress (1.8)
170         */
171        protected $legacy_properties_converted = array();
172
173        /**
174         * @var array Miscellaneous data as set by the __set() magic method
175         * @since BuddyPress (1.8)
176         */
177        protected $data = array();
178
179        /** Screen Overrides **************************************************/
180
181        /**
182         * Screen override methods are how your extension will display content
183         * and handle form submits. Your extension should only override those
184         * methods that it needs for its purposes.
185         */
186
187        // The content of the group tab
188        public function display() {}
189
190        // Content displayed in a widget sidebar, if applicable
191        public function widget_display() {}
192
193        // *_screen() displays the settings form for the given context
194        // *_screen_save() processes data submitted via the settings form
195        // The settings_* methods are generic fallbacks, which can optionally
196        // be overridden by the more specific edit_*, create_*, and admin_*
197        // versions.
198        public function settings_screen( $group_id = null ) {}
199        public function settings_screen_save( $group_id = null ) {}
200        public function edit_screen( $group_id = null ) {}
201        public function edit_screen_save( $group_id = null ) {}
202        public function create_screen( $group_id = null ) {}
203        public function create_screen_save( $group_id = null ) {}
204        public function admin_screen( $group_id = null ) {}
205        public function admin_screen_save( $group_id = null ) {}
206
207        /** Setup *************************************************************/
208
209        /**
210         * Initialize the extension, using your config settings
211         *
212         * Your plugin should call this method at the very end of its
213         * constructor, like so:
214         *
215         *   public function __construct() {
216         *       $args = array(
217         *           'slug' => 'my-group-extension',
218         *           'name' => 'My Group Extension',
219         *           // ...
220         *       );
221         *
222         *       parent::init( $args );
223         *   }
224         *
225         * @since BuddyPress (1.8)
226         * @param array $args See inline definition below for arguments
227         */
228        public function init( $args = array() ) {
229
230                // Before this init() method was introduced, plugins were
231                // encouraged to set their config directly. For backward
232                // compatibility with these plugins, we detect whether this is
233                // one of those legacy plugins, and parse any legacy arguments
234                // with those passed to init()
235                $this->parse_legacy_properties();
236                $args = $this->parse_args_r( $args, $this->legacy_properties_converted );
237
238                // Parse with defaults
239                $this->params = $this->parse_args_r( $args, array(
240                        'slug'                   => '',
241                        'name'                   => '',
242                        'visibility'             => 'public',
243                        'nav_item_position'      => 81,
244                        'enable_nav_item'        => true,
245                        'nav_item_name'          => false,
246                        'display_hook'           => 'groups_custom_group_boxes',
247                        'template_file'          => 'groups/single/plugins',
248                        'screens'                => $this->get_default_screens(),
249                ) );
250
251                $this->initialized = true;
252        }
253
254        /**
255         * The main setup routine for the extension
256         *
257         * This method contains the primary logic for setting up an extension's
258         * configuration, setting up backward compatibility for legacy plugins,
259         * and hooking the extension's screen functions into WP and BP.
260         *
261         * Marked 'public' because it must be accessible to add_action().
262         * However, you should never need to invoke this method yourself - it
263         * is called automatically at the right point in the load order by
264         * bp_register_group_extension().
265         *
266         * @since BuddyPress (1.1)
267         */
268        public function _register() {
269                // Detect and parse properties set by legacy extensions
270                $this->parse_legacy_properties();
271
272                // Initialize, if necessary. This should only happen for
273                // legacy extensions that don't call parent::init() themselves
274                if ( ! $this->initialized ) {
275                        $this->init();
276                }
277
278                // Set some config values, based on the parsed params
279                $this->group_id          = $this->get_group_id();
280                $this->slug              = $this->params['slug'];
281                $this->name              = $this->params['name'];
282                $this->visibility        = $this->params['visibility'];
283                $this->nav_item_position = $this->params['nav_item_position'];
284                $this->nav_item_name     = $this->params['nav_item_name'];
285                $this->display_hook      = $this->params['display_hook'];
286                $this->template_file     = $this->params['template_file'];
287
288                // Configure 'screens': create, admin, and edit contexts
289                $this->setup_screens();
290
291                // Mirror configuration data so it's accessible to plugins
292                // that look for it in its old locations
293                $this->setup_legacy_properties();
294
295                // Hook the extension into BuddyPress
296                $this->setup_display_hooks();
297                $this->setup_create_hooks();
298                $this->setup_edit_hooks();
299                $this->setup_admin_hooks();
300        }
301
302        /**
303         * Set up some basic info about the Extension
304         *
305         * Here we collect the name of the extending class, as well as a
306         * ReflectionClass that is used in get_screen_callback() to determine
307         * whether your extension overrides certain callback methods.
308         *
309         * @since BuddyPress (1.8)
310         */
311        protected function setup_class_info() {
312                if ( is_null( $this->class_name ) ) {
313                        $this->class_name = get_class( $this );
314                }
315
316                if ( is_null( $this->class_reflection ) ) {
317                        $this->class_reflection = new ReflectionClass( $this->class_name );
318                }
319        }
320
321        /**
322         * Get the current group id
323         *
324         * Check for:
325         *   - current group
326         *   - new group
327         *   - group admin
328         *
329         * @since BuddyPress (1.8)
330         */
331        public static function get_group_id() {
332                // Usually this will work
333                $group_id = bp_get_current_group_id();
334
335                // On the admin, get the group id out of the $_GET params
336                if ( ! $group_id && is_admin() && isset( $_GET['page'] ) && 'bp-groups' === $_GET['page'] && ! empty( $_GET['gid'] ) ) {
337                        $group_id = (int) $_GET['gid'];
338                }
339
340                // This fallback will only be hit when the create step is
341                // very early
342                if ( ! $group_id && bp_get_new_group_id() ) {
343                        $group_id = bp_get_new_group_id();
344                }
345
346                // On some setups, the group id has to be fetched out of the
347                // $_POST array
348                // @todo Figure out why this is happening during group creation
349                if ( ! $group_id && isset( $_POST['group_id'] ) ) {
350                        $group_id = (int) $_POST['group_id'];
351                }
352
353                return $group_id;
354        }
355
356        /**
357         * Gather configuration data about your screens
358         *
359         * @since BuddyPress (1.8)
360         */
361        protected function get_default_screens() {
362                $this->setup_class_info();
363
364                $screens = array(
365                        'create' => array(),
366                        'edit'   => array(),
367                        'admin'  => array(
368                                'metabox_context'  => 'normal',
369                                'metabox_priority' => 'core',
370                        ),
371                );
372
373                foreach ( $screens as $context => &$screen ) {
374                        $screen['enabled'] = true;
375
376                        $screen['screen_callback']      = $this->get_screen_callback( $context, 'screen' );
377                        $screen['screen_save_callback'] = $this->get_screen_callback( $context, 'screen_save' );
378
379                        $screen['name']        = '';
380                        $screen['slug']        = '';
381                        $screen['position']    = 81;
382                        $screen['submit_text'] = __( 'Save Changes', 'buddypress' );
383                }
384
385                return $screens;
386        }
387
388        /**
389         * Set up screens array based on params
390         *
391         * @since BuddyPress (1.8)
392         */
393        protected function setup_screens() {
394                foreach ( (array) $this->params['screens'] as $context => $screen ) {
395                        if ( empty( $screen['slug'] ) ) {
396                                $screen['slug'] = $this->slug;
397                        }
398
399                        if ( empty( $screen['name'] ) ) {
400                                $screen['name'] = $this->name;
401                        }
402
403                        $this->screens[ $context ] = $screen;
404                }
405        }
406
407        /** Display *************************************************************/
408
409        /**
410         * Hook this extension's group tab into BuddyPress, if necessary
411         *
412         * @since BuddyPress (1.8)
413         */
414        protected function setup_display_hooks() {
415                if ( ! bp_is_group() ) {
416                        return;
417                }
418
419                if ( 'public' != $this->visibility && ! buddypress()->groups->current_group->user_has_access ) {
420                        false;
421                }
422
423                if ( $this->enable_nav_item ) {
424                        bp_core_new_subnav_item( array(
425                                'name'            => ! $this->nav_item_name ? $this->name : $this->nav_item_name,
426                                'slug'            => $this->slug,
427                                'parent_slug'     => bp_get_current_group_slug(),
428                                'parent_url'      => bp_get_group_permalink( groups_get_current_group() ),
429                                'position'        => $this->nav_item_position,
430                                'item_css_id'     => 'nav-' . $this->slug,
431                                'screen_function' => array( &$this, '_display_hook' ),
432                                'user_has_access' => $this->enable_nav_item
433                        ) );
434
435                        // When we are viewing the extension display page, set the title and options title
436                        if ( bp_is_current_action( $this->slug ) ) {
437                                add_action( 'bp_template_content_header', create_function( '', 'echo "' . esc_attr( $this->name ) . '";' ) );
438                                add_action( 'bp_template_title', create_function( '', 'echo "' . esc_attr( $this->name ) . '";' ) );
439                        }
440                }
441
442                // Hook the group home widget
443                if ( ! bp_current_action() && bp_is_current_action( 'home' ) ) {
444                        add_action( $this->display_hook, array( &$this, 'widget_display' ) );
445                }
446        }
447
448        /**
449         * Hooks the main display method, and loads the template file
450         */
451        public function _display_hook() {
452                add_action( 'bp_template_content', array( &$this, 'display' ) );
453                bp_core_load_template( apply_filters( 'bp_core_template_plugin', $this->template_file ) );
454        }
455
456        /** Create *************************************************************/
457
458        /**
459         * Hook this extension's Create step into BuddyPress, if necessary
460         *
461         * @since BuddyPress (1.8)
462         */
463        protected function setup_create_hooks() {
464                if ( ! $this->is_screen_enabled( 'create' ) ) {
465                        return;
466                }
467
468                $screen = $this->screens['create'];
469
470                // Insert the group creation step for the new group extension
471                buddypress()->groups->group_creation_steps[ $screen['slug'] ] = array(
472                        'name'     => $screen['name'],
473                        'slug'     => $screen['slug'],
474                        'position' => $screen['position'],
475                );
476
477                // The maybe_ methods check to see whether the create_*
478                // callbacks should be invoked (ie, are we on the
479                // correct group creation step). Hooked in separate
480                // methods because current creation step info not yet
481                // available at this point
482                add_action( 'groups_custom_create_steps', array( $this, 'maybe_create_screen' ) );
483                add_action( 'groups_create_group_step_save_' . $screen['slug'], array( $this, 'maybe_create_screen_save' ) );
484        }
485
486        /**
487         * Call the create_screen() method, if we're on the right page
488         *
489         * @since 1.8
490         */
491        public function maybe_create_screen() {
492                if ( bp_is_group_creation_step( $this->screens['create']['slug'] ) ) {
493                        call_user_func( $this->screens['create']['screen_callback'], $this->group_id );
494                        $this->nonce_field( 'create' );
495
496                        // The create screen requires an additional nonce field
497                        // due to a quirk in the way the templates are built
498                        wp_nonce_field( 'groups_create_save_' . bp_get_groups_current_create_step() );
499                }
500        }
501
502        /**
503         * Call the create_screen_save() method, if we're on the right page
504         *
505         * @since 1.8
506         */
507        public function maybe_create_screen_save() {
508                if ( bp_is_group_creation_step( $this->screens['create']['slug'] ) ) {
509                        $this->check_nonce( 'create' );
510                        call_user_func( $this->screens['create']['screen_save_callback'], $this->group_id );
511                }
512        }
513
514        /** Edit *************************************************************/
515
516        /**
517         * Hook this extension's Edit panel into BuddyPress, if necessary
518         *
519         * @since BuddyPress (1.8)
520         */
521        protected function setup_edit_hooks() {
522                if ( ! $this->is_screen_enabled( 'edit' ) || ! bp_is_item_admin() ) {
523                        return;
524                }
525
526                $screen = $this->screens['edit'];
527
528                // Add the tab
529                // @todo BP should be using bp_core_new_subnav_item()
530                add_action( 'groups_admin_tabs', create_function( '$current, $group_slug',
531                        '$selected = "";
532                        if ( "' . esc_attr( $screen['slug'] ) . '" == $current )
533                                $selected = " class=\"current\"";
534                        echo "<li{$selected}><a href=\"' . trailingslashit( bp_get_root_domain() . '/' . bp_get_groups_root_slug() . '/{$group_slug}/admin/' . esc_attr( $screen['slug'] ) ) . '\">' . esc_attr( $screen['name'] ) . '</a></li>";'
535                ), 10, 2 );
536
537                // Catch the edit screen and forward it to the plugin template
538                if ( bp_is_groups_component() && bp_is_current_action( 'admin' ) && bp_is_action_variable( $screen['slug'], 0 ) ) {
539                        $this->call_edit_screen_save( $this->group_id );
540
541                        add_action( 'groups_custom_edit_steps', array( &$this, 'call_edit_screen' ) );
542
543                        if ( '' != bp_locate_template( array( 'groups/single/home.php' ), false ) ) {
544                                bp_core_load_template( apply_filters( 'groups_template_group_home', 'groups/single/home' ) );
545                        } else {
546                                add_action( 'bp_template_content_header', create_function( '', 'echo "<ul class=\"content-header-nav\">"; bp_group_admin_tabs(); echo "</ul>";' ) );
547                                add_action( 'bp_template_content', array( &$this, 'call_edit_screen' ) );
548                                bp_core_load_template( apply_filters( 'bp_core_template_plugin', '/groups/single/plugins' ) );
549                        }
550                }
551        }
552
553        /**
554         * Call the edit_screen() method
555         *
556         * Previous versions of BP_Group_Extension required plugins to provide
557         * their own Submit button and nonce fields when building markup. In
558         * BP 1.8, this requirement was lifted - BP_Group_Extension now handles
559         * all required submit buttons and nonces.
560         *
561         * We put the edit screen markup into an output buffer before echoing.
562         * This is so that we can check for the presence of a hardcoded submit
563         * button, as would be present in legacy plugins; if one is found, we
564         * do not auto-add our own button.
565         *
566         * @since BuddyPress (1.8)
567         */
568        public function call_edit_screen() {
569                ob_start();
570                call_user_func( $this->screens['edit']['screen_callback'], $this->group_id );
571                $screen = ob_get_contents();
572                ob_end_clean();
573
574                echo $this->maybe_add_submit_button( $screen );
575
576                $this->nonce_field( 'edit' );
577        }
578
579        /**
580         * Check the nonce, and call the edit_screen_save() method
581         *
582         * @since BuddyPress (1.8)
583         */
584        public function call_edit_screen_save() {
585                if ( ! empty( $_POST ) ) {
586                        $this->check_nonce( 'edit' );
587                        call_user_func( $this->screens['edit']['screen_save_callback'], $this->group_id );
588                }
589        }
590
591        /**
592         * Add a submit button to the edit form, if it needs one
593         *
594         * There's an inconsistency in the way that the group Edit and Create
595         * screens are rendered: the Create screen has a submit button built
596         * in, but the Edit screen does not. This function allows plugin
597         * authors to write markup that does not contain the submit button for
598         * use on both the Create and Edit screens - BP will provide the button
599         * if one is not found.
600         *
601         * @since BuddyPress (1.8)
602         * @param string $screen The screen markup, captured in the output buffer
603         * @param string $screen The same markup, with a submit button added
604         */
605        protected function maybe_add_submit_button( $screen ) {
606                if ( ! $this->has_submit_button( $screen ) ) {
607                        $screen .= sprintf(
608                                '<div id="%s"><input type="submit" name="save" value="%s" id="%s"></div>',
609                                'bp-group-edit-' . $this->slug . '-submit-wrapper',
610                                $this->screens['edit']['submit_text'],
611                                'bp-group-edit-' . $this->slug . '-submit'
612                        );
613                }
614
615                return $screen;
616        }
617
618        /**
619         * Does the given markup have a submit button?
620         *
621         * @since BuddyPress (1.8)
622         * @param $screen The markup to check
623         * @return bool
624         */
625        public static function has_submit_button( $screen ) {
626                $pattern = "/<input[^>]+type=[\'\"]submit[\'\"]/";
627                preg_match( $pattern, $screen, $matches );
628                return ! empty( $matches[0] );
629        }
630
631        /** Admin *************************************************************/
632
633        /**
634         * Hook this extension's Admin metabox into BuddyPress, if necessary
635         *
636         * @since BuddyPress (1.8)
637         */
638        protected function setup_admin_hooks() {
639                if ( $this->is_screen_enabled( 'admin' ) && is_admin() ) {
640                        // Hook the admin screen markup function to the content hook
641                        add_action( 'bp_groups_admin_meta_box_content_' . $this->slug, array( $this, 'call_admin_screen' ) );
642
643                        // Initialize the metabox
644                        add_action( 'bp_groups_admin_meta_boxes', array( $this, '_meta_box_display_callback' ) );
645
646                        // Catch the metabox save
647                        add_action( 'bp_group_admin_edit_after', array( $this, 'call_admin_screen_save' ), 10 );
648                }
649        }
650
651        /**
652         * Call the admin_screen() method, and add a nonce field
653         *
654         * @since BuddyPress (1.8)
655         */
656        public function call_admin_screen( $group_id = null ) {
657                call_user_func( $this->screens['admin']['screen_callback'], $this->group_id );
658                $this->nonce_field( 'admin' );
659        }
660
661        /**
662         * Check the nonce, and call the admin_screen_save() method
663         *
664         * @since BuddyPress (1.8)
665         */
666        public function call_admin_screen_save( $group_id = null ) {
667                $this->check_nonce( 'admin' );
668                call_user_func( $this->screens['admin']['screen_save_callback'], $this->group_id );
669        }
670
671        /**
672         * Create the Dashboard meta box for this extension
673         *
674         * @since BuddyPress (1.7)
675         */
676        public function _meta_box_display_callback() {
677                $group_id = isset( $_GET['gid'] ) ? (int) $_GET['gid'] : 0;
678
679                $screen = $this->screens['admin'];
680
681                add_meta_box(
682                        $screen['slug'],
683                        $screen['name'],
684                        create_function( '', 'do_action( "bp_groups_admin_meta_box_content_' . $this->slug . '", ' . $group_id . ' );' ),
685                        get_current_screen()->id,
686                        $screen['metabox_context'],
687                        $screen['metabox_priority']
688                );
689        }
690
691
692        /** Utilities *********************************************************/
693
694        /**
695         * Generate the nonce fields for a settings form
696         *
697         * The nonce field name (the second param passed to wp_nonce_field)
698         * contains this extension's slug and is thus unique to this extension.
699         * This is necessary because in some cases (namely, the Dashboard),
700         * more than one extension may generate nonces on the same page, and we
701         * must avoid name clashes.
702         *
703         * @since BuddyPress (1.8)
704         * @uses wp_nonce_field()
705         * @param string $context 'create', 'edit', 'admin'
706         */
707        public function nonce_field( $context = '' ) {
708                wp_nonce_field( 'bp_group_extension_' . $this->slug . '_' . $context, '_bp_group_' . $context . '_nonce_' . $this->slug );
709        }
710
711        /**
712         * Check the nonce on a submitted settings form
713         *
714         * @since BuddyPress (1.8)
715         * @uses check_admin_referer()
716         * @param string $context 'create', 'edit', 'admin'
717         */
718        public function check_nonce( $context = '' ) {
719                check_admin_referer( 'bp_group_extension_' . $this->slug . '_' . $context, '_bp_group_' . $context . '_nonce_' . $this->slug );
720        }
721
722        /**
723         * Is the specified screen enabled?
724         *
725         * To be enabled, a screen must both have the 'enabled' key set to true
726         * (legacy: $this->enable_create_step, etc), and its screen_callback
727         * must also exist and be callable.
728         *
729         * @since BuddyPress (1.8)
730         * @param string $context 'create', 'edit', 'admin'
731         * @return bool
732         */
733        public function is_screen_enabled( $context = '' ) {
734                $enabled = false;
735
736                if ( isset( $this->screens[ $context ] ) ) {
737                        $enabled = $this->screens[ $context ]['enabled'] && is_callable( $this->screens[ $context ]['screen_callback'] );
738                }
739
740                return $enabled;
741        }
742
743        /**
744         * Get the appropriate screen callback for the specified context/type
745         *
746         * BP Group Extensions have three special "screen contexts": create,
747         * admin, and edit. Each of these contexts has a corresponding
748         * _screen() and _screen_save() method, which allow group extension
749         * plugins to define different markup and logic for each context.
750         *
751         * BP also supports fallback settings_screen() and
752         * settings_screen_save() methods, which can be used to define markup
753         * and logic that is shared between context. For each context, you may
754         * either provide context-specific methods, or you can let BP fall back
755         * on the shared settings_* callbacks.
756         *
757         * For example, consider a BP_Group_Extension implementation that looks
758         * like this:
759         *
760         *   // ...
761         *   function create_screen( $group_id ) { ... }
762         *   function create_screen_save( $group_id ) { ... }
763         *   function settings_screen( $group_id ) { ... }
764         *   function settings_screen_save( $group_id ) { ... }
765         *   // ...
766         *
767         * BP_Group_Extension will use your create_* methods for the Create
768         * steps, and will use your generic settings_* methods for the Edit
769         * and Admin contexts. This schema allows plugin authors maximum
770         * flexibility without having to repeat themselves.
771         *
772         * The get_screen_callback() method uses a ReflectionClass object to
773         * determine whether your extension has provided a given callback.
774         *
775         * @since BuddyPress (1.8)
776         * @param string $context 'create', 'edit', 'admin'
777         * @param string $type 'screen', 'screen_save'
778         * @return mixed A callable function handle
779         */
780        public function get_screen_callback( $context = '', $type = 'screen' ) {
781                $callback = '';
782
783                // Try the context-specific callback first
784                $method  = $context . '_' . $type;
785                $rmethod = $this->class_reflection->getMethod( $method );
786                if ( isset( $rmethod->class ) && $this->class_name === $rmethod->class ) {
787                        $callback = array( $this->class_name, $method );
788                }
789
790                if ( ! $callback ) {
791                        $fallback_method  = 'settings_' . $type;
792                        $rfallback_method = $this->class_reflection->getMethod( $fallback_method );
793                        if ( isset( $rfallback_method->class ) && $this->class_name === $rfallback_method->class ) {
794                                $callback = array( $this->class_name, $fallback_method );
795                        }
796                }
797
798                return $callback;
799        }
800
801        /**
802         * Recursive argument parsing
803         *
804         * This acts like a multi-dimensional version of wp_parse_args() (minus
805         * the querystring parsing - you must pass arrays).
806         *
807         * Values from $a override those from $b; keys in $b that don't exist
808         * in $a are passed through.
809         *
810         * This is different from array_merge_recursive(), both because of the
811         * order of preference ($a overrides $b) and because of the fact that
812         * array_merge_recursive() combines arrays deep in the tree, rather
813         * than overwriting the b array with the a array.
814         *
815         * The implementation of this function is specific to the needs of
816         * BP_Group_Extension, where we know that arrays will always be
817         * associative, and that an argument under a given key in one array
818         * will be matched by a value of identical depth in the other one. The
819         * function is NOT designed for general use, and will probably result
820         * in unexpected results when used with data in the wild. See, eg,
821         * http://core.trac.wordpress.org/ticket/19888
822         *
823         * @since BuddyPress (1.8)
824         * @arg array $a
825         * @arg array $b
826         * @return array
827         */
828        public static function parse_args_r( &$a, $b ) {
829                $a = (array) $a;
830                $b = (array) $b;
831                $r = $b;
832
833                foreach ( $a as $k => &$v ) {
834                        if ( is_array( $v ) && isset( $r[ $k ] ) ) {
835                                $r[ $k ] = self::parse_args_r( $v, $r[ $k ] );
836                        } else {
837                                $r[ $k ] = $v;
838                        }
839                }
840
841                return $r;
842        }
843
844        /** Legacy Support ****************************************************/
845
846        /**
847         * In BuddyPress 1.8, the recommended technique for configuring
848         * extensions changed from directly setting various object properties
849         * in the class constructor, to passing a configuration array to
850         * parent::init(). The following methods ensure that extensions created
851         * in the old way continue to work, by converting legacy configuration
852         * data to the new format.
853         */
854
855        /**
856         * Provide access to otherwise unavailable object properties
857         *
858         * This magic method is here for backward compatibility with plugins
859         * that refer to config properties that have moved to a different
860         * location (such as enable_create_step, which is now at
861         * $this->screens['create']['enabled']
862         *
863         * The legacy_properties array is set up in
864         * self::setup_legacy_properties().
865         *
866         * @since BuddyPress (1.8)
867         * @param string $key
868         * @return mixed
869         */
870        public function __get( $key ) {
871                if ( isset( $this->legacy_properties[ $key ] ) ) {
872                        return $this->legacy_properties[ $key ];
873                } else if ( isset( $this->data[ $key ] ) ) {
874                        return $this->data[ $key ];
875                } else {
876                        return null;
877                }
878        }
879
880        /**
881         * Allow plugins to set otherwise unavailable object properties
882         *
883         * This magic method is here for backward compatibility with plugins
884         * that may attempt to modify the group extension by manually assigning
885         * a value to an object property that no longer exists, such as
886         * $this->enable_create_step.
887         *
888         * @since BuddyPress (1.8)
889         * @param string $key
890         * @param mixed $value
891         */
892        public function __set( $key, $value ) {
893
894                if ( ! $this->is_initialized ) {
895                        $this->data[ $key ] = $value;
896                }
897
898                switch ( $key ) {
899                        case 'enable_create_step' :
900                                $this->screens['create']['enabled'] = $value;
901                                break;
902
903                        case 'enable_edit_item' :
904                                $this->screens['edit']['enabled'] = $value;
905                                break;
906
907                        case 'enable_admin_item' :
908                                $this->screens['admin']['enabled'] = $value;
909                                break;
910
911                        case 'create_step_position' :
912                                $this->screens['create']['position'] = $value;
913                                break;
914
915                        // Note: 'admin' becomes 'edit' to distinguish from Dashboard 'admin'
916                        case 'admin_name' :
917                                $this->screens['edit']['name'] = $value;
918                                break;
919
920                        case 'admin_slug' :
921                                $this->screens['edit']['slug'] = $value;
922                                break;
923
924                        case 'create_name' :
925                                $this->screens['create']['name'] = $value;
926                                break;
927
928                        case 'create_slug' :
929                                $this->screens['create']['slug'] = $value;
930                                break;
931
932                        case 'admin_metabox_context' :
933                                $this->screens['admin']['metabox_context'] = $value;
934                                break;
935
936                        case 'admin_metabox_priority' :
937                                $this->screens['admin']['metabox_priority'] = $value;
938                                break;
939
940                        default :
941                                $this->data[ $key ] = $value;
942                                break;
943                }
944        }
945
946        /**
947         * Returns a list of legacy properties
948         *
949         * The legacy implementation of BP_Group_Extension used all of these
950         * object properties for configuration. Some have been moved.
951         *
952         * @since BuddyPress (1.8)
953         * @return array
954         */
955        protected function get_legacy_property_list() {
956                return array(
957                        'name',
958                        'slug',
959                        'admin_name',
960                        'admin_slug',
961                        'create_name',
962                        'create_slug',
963                        'visibility',
964                        'create_step_position',
965                        'nav_item_position',
966                        'admin_metabox_context',
967                        'admin_metabox_priority',
968                        'enable_create_step',
969                        'enable_nav_item',
970                        'enable_edit_item',
971                        'enable_admin_item',
972                        'nav_item_name',
973                        'display_hook',
974                        'template_file',
975                );
976        }
977
978        /**
979         * Parse legacy properties
980         *
981         * The old standard for BP_Group_Extension was for plugins to register
982         * their settings as properties in their constructor. The new method is
983         * to pass a config array to the init() method. In order to support
984         * legacy plugins, we slurp up legacy properties, and later on we'll
985         * parse them into the new init() array.
986         *
987         * @since BuddyPress (1.8)
988         */
989        protected function parse_legacy_properties() {
990
991                // Only run this one time
992                if ( empty( $this->legacy_properties_converted ) ) {
993                        $properties = $this->get_legacy_property_list();
994
995                        // By-reference variable for convenience
996                        $lpc =& $this->legacy_properties_converted;
997
998                        foreach ( $properties as $property ) {
999                                // No legacy config exists for this key
1000                                if ( ! isset( $this->{$property} ) ) {
1001                                        continue;
1002                                }
1003
1004                                // Grab the value and record it as appropriate
1005                                $value = $this->{$property};
1006
1007                                switch ( $property ) {
1008                                        case 'enable_create_step' :
1009                                                $lpc['screens']['create']['enabled'] = (bool) $value;
1010                                                break;
1011
1012                                        case 'enable_edit_item' :
1013                                                $lpc['screens']['edit']['enabled'] = (bool) $value;
1014                                                break;
1015
1016                                        case 'enable_admin_item' :
1017                                                $lpc['screens']['admin']['enabled'] = (bool) $value;
1018                                                break;
1019
1020                                        case 'create_step_position' :
1021                                                $lpc['screens']['create']['position'] = $value;
1022                                                break;
1023
1024                                        // Note: 'admin' becomes 'edit' to distinguish from Dashboard 'admin'
1025                                        case 'admin_name' :
1026                                                $lpc['screens']['edit']['name'] = $value;
1027                                                break;
1028
1029                                        case 'admin_slug' :
1030                                                $lpc['screens']['edit']['slug'] = $value;
1031                                                break;
1032
1033                                        case 'create_name' :
1034                                                $lpc['screens']['create']['name'] = $value;
1035                                                break;
1036
1037                                        case 'create_slug' :
1038                                                $lpc['screens']['create']['slug'] = $value;
1039                                                break;
1040
1041                                        case 'admin_metabox_context' :
1042                                                $lpc['screens']['admin']['metabox_context'] = $value;
1043                                                break;
1044
1045                                        case 'admin_metabox_priority' :
1046                                                $lpc['screens']['admin']['metabox_priority'] = $value;
1047                                                break;
1048
1049                                        default :
1050                                                $lpc[ $property ] = $value;
1051                                                break;
1052                                }
1053                        }
1054                }
1055        }
1056
1057        /**
1058         * Set up legacy properties
1059         *
1060         * This method is responsible for ensuring that all legacy config
1061         * properties are stored in an array $this->legacy_properties, so that
1062         * they remain available to plugins that reference the variables at
1063         * their old locations.
1064         *
1065         * @see self::__get()
1066         *
1067         * @since BuddyPress (1.8)
1068         */
1069        protected function setup_legacy_properties() {
1070
1071                // Only run this one time
1072                if ( empty( $this->legacy_properties ) ) {
1073                        $properties = $this->get_legacy_property_list();
1074                        $params     = $this->params;
1075                        $lp         =& $this->legacy_properties;
1076
1077                        foreach ( $properties as $property ) {
1078                                switch ( $property ) {
1079                                        case 'enable_create_step' :
1080                                                $lp['enable_create_step'] = $params['screens']['create']['enabled'];
1081                                                break;
1082
1083                                        case 'enable_edit_item' :
1084                                                $lp['enable_edit_item'] = $params['screens']['edit']['enabled'];
1085                                                break;
1086
1087                                        case 'enable_admin_item' :
1088                                                $lp['enable_admin_item'] = $params['screens']['admin']['enabled'];
1089                                                break;
1090
1091                                        case 'create_step_position' :
1092                                                $lp['create_step_position'] = $params['screens']['create']['position'];
1093                                                break;
1094
1095                                        // Note: 'admin' becomes 'edit' to distinguish from Dashboard 'admin'
1096                                        case 'admin_name' :
1097                                                $lp['admin_name'] = $params['screens']['edit']['name'];
1098                                                break;
1099
1100                                        case 'admin_slug' :
1101                                                $lp['admin_slug'] = $params['screens']['edit']['slug'];
1102                                                break;
1103
1104                                        case 'create_name' :
1105                                                $lp['create_name'] = $params['screens']['create']['name'];
1106                                                break;
1107
1108                                        case 'create_slug' :
1109                                                $lp['create_slug'] = $params['screens']['create']['slug'];
1110                                                break;
1111
1112                                        case 'admin_metabox_context' :
1113                                                $lp['admin_metabox_context'] = $params['screens']['admin']['metabox_context'];
1114                                                break;
1115
1116                                        case 'admin_metabox_priority' :
1117                                                $lp['admin_metabox_priority'] = $params['screens']['admin']['metabox_priority'];
1118                                                break;
1119
1120                                        default :
1121                                                // All other items get moved over
1122                                                $lp[ $property ] = $params[ $property ];
1123
1124                                                // Also reapply to the object, for backpat
1125                                                $this->{$property} = $params[ $property ];
1126
1127                                                break;
1128                                }
1129                        }
1130                }
1131        }
1132}
1133
1134