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