/bp-groups/classes/class-bp-group-extension.php

  1. <?php 
  2. /** 
  3. * BuddyPress Groups Classes. 
  4. * 
  5. * @package BuddyPress 
  6. * @subpackage GroupsClasses 
  7. * @since 1.1.0 
  8. */ 
  9.  
  10. // Exit if accessed directly. 
  11. defined( 'ABSPATH' ) || exit; 
  12.  
  13. /** 
  14. * API for creating group extensions without having to hardcode the content into 
  15. * the theme. 
  16. * 
  17. * To implement, extend this class. In your constructor, pass an optional array 
  18. * of arguments to parent::init() to configure your widget. The config array 
  19. * supports the following values: 
  20. * - 'slug' A unique identifier for your extension. This value will be used 
  21. * to build URLs, so make it URL-safe. 
  22. * - 'name' A translatable name for your extension. This value is used to 
  23. * populate the navigation tab, as well as the default titles for admin/ 
  24. * edit/create tabs. 
  25. * - 'visibility' Set to 'public' (default) for your extension (the main tab 
  26. * as well as the widget) to be available to anyone who can access the 
  27. * group, 'private' otherwise. 
  28. * - 'nav_item_position' An integer explaining where the nav item should 
  29. * appear in the tab list. 
  30. * - 'enable_nav_item' Set to true for your extension's main tab to be 
  31. * available to anyone who can access the group. 
  32. * - 'nav_item_name' The translatable text you want to appear in the nav tab. 
  33. * Defaults to the value of 'name'. 
  34. * - 'display_hook' The WordPress action that the widget_display() method is 
  35. * hooked to. 
  36. * - 'template_file' The template file that will be used to load the content 
  37. * of your main extension tab. Defaults to 'groups/single/plugins.php'. 
  38. * - 'screens' A multi-dimensional array, described below. 
  39. * - 'access' Which users can visit the plugin's tab. 
  40. * - 'show_tab' Which users can see the plugin's navigation tab. 
  41. * 
  42. * BP_Group_Extension uses the concept of "settings screens". There are three 
  43. * contexts for settings screens: 
  44. * - 'create', which inserts a new step into the group creation process 
  45. * - 'edit', which adds a tab for your extension into the Admin section of 
  46. * a group 
  47. * - 'admin', which adds a metabox to the Groups administration panel in the 
  48. * WordPress Dashboard 
  49. * Each of these settings screens is populated by a pair of methods: one that 
  50. * creates the markup for the screen, and one that processes form data 
  51. * submitted from the screen. If your plugin needs screens in all three 
  52. * contexts, and if the markup and form processing logic will be the same in 
  53. * each case, you can define two methods to handle all of the screens: 
  54. * function settings_screen() {} 
  55. * function settings_screen_save() {} 
  56. * If one or more of your settings screen needs separate logic, you may define 
  57. * context-specific methods, for example: 
  58. * function edit_screen() {} 
  59. * function edit_screen_save() {} 
  60. * BP_Group_Extension will use the more specific methods if they are available. 
  61. * 
  62. * You can further customize the settings screens (tab names, etc) by passing 
  63. * an optional 'screens' parameter to the init array. The format is as follows: 
  64. * 'screens' => array( 
  65. * 'create' => array( 
  66. * 'slug' => 'foo',  
  67. * 'name' => 'Foo',  
  68. * 'position' => 55,  
  69. * 'screen_callback' => 'my_create_screen_callback',  
  70. * 'screen_save_callback' => 'my_create_screen_save_callback',  
  71. * ),  
  72. * 'edit' => array( // ... 
  73. * ),  
  74. * Only provide those arguments that you actually want to change from the 
  75. * default configuration. BP_Group_Extension will do the rest. 
  76. * 
  77. * Note that the 'edit' screen accepts an additional parameter: 'submit_text',  
  78. * which defines the text of the Submit button automatically added to the Edit 
  79. * screen of the extension (defaults to 'Save Changes'). Also, the 'admin' 
  80. * screen accepts two additional parameters: 'metabox_priority' and 
  81. * 'metabox_context'. See the docs for add_meta_box() for more details on these 
  82. * arguments. 
  83. * 
  84. * Prior to BuddyPress 1.7, group extension configurations were set slightly 
  85. * differently. The legacy method is still supported, though deprecated. 
  86. * 
  87. * @since 1.1.0 
  88. */ 
  89. class BP_Group_Extension { 
  90.  
  91. /** Public ************************************************************/ 
  92.  
  93. /** 
  94. * Information about this extension's screens. 
  95. * 
  96. * @since 1.8.0 
  97. * @var array 
  98. */ 
  99. public $screens = array(); 
  100.  
  101. /** 
  102. * The name of the extending class. 
  103. * 
  104. * @since 1.8.0 
  105. * @var string 
  106. */ 
  107. public $class_name = ''; 
  108.  
  109. /** 
  110. * A ReflectionClass object of the current extension. 
  111. * 
  112. * @since 1.8.0 
  113. * @var ReflectionClass 
  114. */ 
  115. public $class_reflection = null; 
  116.  
  117. /** 
  118. * Parsed configuration parameters for the extension. 
  119. * 
  120. * @since 1.8.0 
  121. * @var array 
  122. */ 
  123. public $params = array(); 
  124.  
  125. /** 
  126. * Raw config params, as passed by the extending class. 
  127. * 
  128. * @since 2.1.0 
  129. * @var array 
  130. */ 
  131. public $params_raw = array(); 
  132.  
  133. /** 
  134. * The ID of the current group. 
  135. * 
  136. * @since 1.8.0 
  137. * @var int 
  138. */ 
  139. public $group_id = 0; 
  140.  
  141. /** 
  142. * The slug of the current extension. 
  143. * 
  144. * @since 1.1.0 
  145. * @var string 
  146. */ 
  147. public $slug = ''; 
  148.  
  149. /** 
  150. * The translatable name of the current extension. 
  151. * 
  152. * @since 1.1.0 
  153. * @var string 
  154. */ 
  155. public $name = ''; 
  156.  
  157. /** 
  158. * The visibility of the extension tab. 'public' or 'private'. 
  159. * 
  160. * @since 1.1.0 
  161. * @var string 
  162. */ 
  163. public $visibility = 'public'; 
  164.  
  165. /** 
  166. * The numeric position of the main nav item. 
  167. * 
  168. * @since 1.1.0 
  169. * @var int 
  170. */ 
  171. public $nav_item_position = 81; 
  172.  
  173. /** 
  174. * Whether to show the nav item. 
  175. * 
  176. * @since 1.1.0 
  177. * @var bool 
  178. */ 
  179. public $enable_nav_item = true; 
  180.  
  181. /** 
  182. * Whether the current user should see the navigation item. 
  183. * 
  184. * @since 2.1.0 
  185. * @var bool 
  186. */ 
  187. public $user_can_see_nav_item; 
  188.  
  189. /** 
  190. * Whether the current user can visit the tab. 
  191. * 
  192. * @since 2.1.0 
  193. * @var bool 
  194. */ 
  195. public $user_can_visit; 
  196.  
  197. /** 
  198. * The text of the nav item. Defaults to self::name. 
  199. * 
  200. * @since 1.1.0 
  201. * @var string 
  202. */ 
  203. public $nav_item_name = ''; 
  204.  
  205. /** 
  206. * The WP action that self::widget_display() is attached to. 
  207. * 
  208. * Default: 'groups_custom_group_boxes'. 
  209. * 
  210. * @since 1.1.0 
  211. * @var string 
  212. */ 
  213. public $display_hook = 'groups_custom_group_boxes'; 
  214.  
  215. /** 
  216. * The template file used to load the plugin content. 
  217. * 
  218. * Default: 'groups/single/plugins'. 
  219. * 
  220. * @since 1.1.0 
  221. * @var string 
  222. */ 
  223. public $template_file = 'groups/single/plugins'; 
  224.  
  225. /** Protected *********************************************************/ 
  226.  
  227. /** 
  228. * Has the extension been initialized? 
  229. * 
  230. * @since 1.8.0 
  231. * @var bool 
  232. */ 
  233. protected $initialized = false; 
  234.  
  235. /** 
  236. * Extension properties as set by legacy extensions. 
  237. * 
  238. * @since 1.8.0 
  239. * @var array 
  240. */ 
  241. protected $legacy_properties = array(); 
  242.  
  243. /** 
  244. * Converted legacy parameters. 
  245. * 
  246. * These are the extension properties as set by legacy extensions, but 
  247. * then converted to match the new format for params. 
  248. * 
  249. * @since 1.8.0 
  250. * @var array 
  251. */ 
  252. protected $legacy_properties_converted = array(); 
  253.  
  254. /** 
  255. * Redirect location as defined by post-edit save callback. 
  256. * 
  257. * @since 2.1.0 
  258. * @var string 
  259. */ 
  260. protected $post_save_redirect; 
  261.  
  262. /** 
  263. * Miscellaneous data as set by the __set() magic method. 
  264. * 
  265. * @since 1.8.0 
  266. * @var array 
  267. */ 
  268. protected $data = array(); 
  269.  
  270. /** Screen Overrides **************************************************/ 
  271.  
  272. /** 
  273. * Screen override methods are how your extension will display content 
  274. * and handle form submits. Your extension should only override those 
  275. * methods that it needs for its purposes. 
  276. */ 
  277.  
  278. /** 
  279. * The content of the group tab. 
  280. * 
  281. * @since 1.1.0 
  282. * 
  283. * @param int|null $group_id ID of the group to display. 
  284. */ 
  285. public function display( $group_id = null ) {} 
  286.  
  287. /** 
  288. * Content displayed in a widget sidebar, if applicable. 
  289. * 
  290. * @since 1.1.0 
  291. */ 
  292. public function widget_display() {} 
  293.  
  294. /** 
  295. * *_screen() displays the settings form for the given context 
  296. * *_screen_save() processes data submitted via the settings form 
  297. * The settings_* methods are generic fallbacks, which can optionally 
  298. * be overridden by the more specific edit_*, create_*, and admin_* 
  299. * versions. 
  300. */ 
  301. public function settings_screen( $group_id = null ) {} 
  302. public function settings_screen_save( $group_id = null ) {} 
  303. public function edit_screen( $group_id = null ) {} 
  304. public function edit_screen_save( $group_id = null ) {} 
  305. public function create_screen( $group_id = null ) {} 
  306. public function create_screen_save( $group_id = null ) {} 
  307. public function admin_screen( $group_id = null ) {} 
  308. public function admin_screen_save( $group_id = null ) {} 
  309.  
  310. /** Setup *************************************************************/ 
  311.  
  312. /** 
  313. * Initialize the extension, using your config settings. 
  314. * 
  315. * Your plugin should call this method at the very end of its 
  316. * constructor, like so: 
  317. * 
  318. * public function __construct() { 
  319. * $args = array( 
  320. * 'slug' => 'my-group-extension',  
  321. * 'name' => 'My Group Extension',  
  322. * // ... 
  323. * ); 
  324. * 
  325. * parent::init( $args ); 
  326. * } 
  327. * 
  328. * @since 1.8.0 
  329. * @since 2.1.0 Added 'access' and 'show_tab' arguments to `$args`. 
  330. * 
  331. * @param array $args { 
  332. * Array of initialization arguments. 
  333. * @type string $slug Unique, URL-safe identifier for your extension. 
  334. * @type string $name Translatable name for your extension. Used to populate 
  335. * navigation items. 
  336. * @type string $visibility Optional. Set to 'public' for your extension (the main tab as well 
  337. * as the widget) to be available to anyone who can access the group; 
  338. * set to 'private' otherwise. Default: 'public'. 
  339. * @type int $nav_item_position Optional. Location of the nav item in the tab list. 
  340. * Default: 81. 
  341. * @type bool $enable_nav_item Optional. Whether the extension's tab should be accessible to 
  342. * anyone who can view the group. Default: true. 
  343. * @type string $nav_item_name Optional. The translatable text you want to appear in the nav tab. 
  344. * Default: the value of `$name`. 
  345. * @type string $display_hook Optional. The WordPress action that the widget_display() method is 
  346. * hooked to. Default: 'groups_custom_group_boxes'. 
  347. * @type string $template_file Optional. Theme-relative path to the template file BP should use 
  348. * to load the content of your main extension tab. 
  349. * Default: 'groups/single/plugins.php'. 
  350. * @type array $screens A multi-dimensional array of configuration information for the 
  351. * extension screens. See docblock of {@link BP_Group_Extension} 
  352. * for more details. 
  353. * @type string|array $access Which users can visit the plugin's tab. Possible values: 'anyone',  
  354. * 'loggedin', 'member', 'mod', 'admin' or 'noone'. ('member', 'mod',  
  355. * 'admin' refer to user's role in group.) Note that 'mod' targets 
  356. * only group moderators. If you want to allow access to group moderators 
  357. * and admins, specify `array( 'mod', 'admin' )`. Defaults to 'anyone' 
  358. * for public groups and 'member' for private groups. 
  359. * @type string|array $show_tab Which users can see the plugin's navigation tab. Possible values: 
  360. * 'anyone', 'loggedin', 'member', 'mod', 'admin' or 'noone'. 
  361. * ('member', 'mod', 'admin' refer to user's role in group.) Note 
  362. * that 'mod' targets only group moderators. If you want to show the 
  363. * tab to group moderators and admins, specify 
  364. * `array( 'mod', 'admin' )`. Defaults to 'anyone' for public groups 
  365. * and 'member' for private groups. 
  366. * } 
  367. */ 
  368. public function init( $args = array() ) { 
  369. // Store the raw arguments. 
  370. $this->params_raw = $args; 
  371.  
  372. // Before this init() method was introduced, plugins were 
  373. // encouraged to set their config directly. For backward 
  374. // compatibility with these plugins, we detect whether this is 
  375. // one of those legacy plugins, and parse any legacy arguments 
  376. // with those passed to init(). 
  377. $this->parse_legacy_properties(); 
  378. $args = $this->parse_args_r( $args, $this->legacy_properties_converted ); 
  379.  
  380. // Parse with defaults. 
  381. $this->params = $this->parse_args_r( $args, array( 
  382. 'slug' => $this->slug,  
  383. 'name' => $this->name,  
  384. 'visibility' => $this->visibility,  
  385. 'nav_item_position' => $this->nav_item_position,  
  386. 'enable_nav_item' => (bool) $this->enable_nav_item,  
  387. 'nav_item_name' => $this->nav_item_name,  
  388. 'display_hook' => $this->display_hook,  
  389. 'template_file' => $this->template_file,  
  390. 'screens' => $this->get_default_screens(),  
  391. 'access' => null,  
  392. 'show_tab' => null,  
  393. ) ); 
  394.  
  395. $this->initialized = true; 
  396.  
  397. /** 
  398. * The main setup routine for the extension. 
  399. * 
  400. * This method contains the primary logic for setting up an extension's 
  401. * configuration, setting up backward compatibility for legacy plugins,  
  402. * and hooking the extension's screen functions into WP and BP. 
  403. * 
  404. * Marked 'public' because it must be accessible to add_action(). 
  405. * However, you should never need to invoke this method yourself - it 
  406. * is called automatically at the right point in the load order by 
  407. * bp_register_group_extension(). 
  408. * 
  409. * @since 1.1.0 
  410. */ 
  411. public function _register() { 
  412.  
  413. // Detect and parse properties set by legacy extensions. 
  414. $this->parse_legacy_properties(); 
  415.  
  416. // Initialize, if necessary. This should only happen for 
  417. // legacy extensions that don't call parent::init() themselves. 
  418. if ( true !== $this->initialized ) { 
  419. $this->init(); 
  420.  
  421. // Set some config values, based on the parsed params. 
  422. $this->group_id = $this->get_group_id(); 
  423. $this->slug = $this->params['slug']; 
  424. $this->name = $this->params['name']; 
  425. $this->visibility = $this->params['visibility']; 
  426. $this->nav_item_position = $this->params['nav_item_position']; 
  427. $this->nav_item_name = $this->params['nav_item_name']; 
  428. $this->display_hook = $this->params['display_hook']; 
  429. $this->template_file = $this->params['template_file']; 
  430.  
  431. // Configure 'screens': create, admin, and edit contexts. 
  432. $this->setup_screens(); 
  433.  
  434. // Configure access-related settings. 
  435. $this->setup_access_settings(); 
  436.  
  437. // Mirror configuration data so it's accessible to plugins 
  438. // that look for it in its old locations. 
  439. $this->setup_legacy_properties(); 
  440.  
  441. // Hook the extension into BuddyPress. 
  442. $this->setup_display_hooks(); 
  443. $this->setup_create_hooks(); 
  444. $this->setup_edit_hooks(); 
  445. $this->setup_admin_hooks(); 
  446.  
  447. /** 
  448. * Set up some basic info about the Extension. 
  449. * 
  450. * Here we collect the name of the extending class, as well as a 
  451. * ReflectionClass that is used in get_screen_callback() to determine 
  452. * whether your extension overrides certain callback methods. 
  453. * 
  454. * @since 1.8.0 
  455. */ 
  456. protected function setup_class_info() { 
  457. if ( empty( $this->class_name ) ) { 
  458. $this->class_name = get_class( $this ); 
  459.  
  460. if ( is_null( $this->class_reflection ) ) { 
  461. $this->class_reflection = new ReflectionClass( $this->class_name ); 
  462.  
  463. /** 
  464. * Get the current group ID. 
  465. * 
  466. * Check for: 
  467. * - current group 
  468. * - new group 
  469. * - group admin 
  470. * 
  471. * @since 1.8.0 
  472. * 
  473. * @return int 
  474. */ 
  475. public static function get_group_id() { 
  476.  
  477. // Usually this will work. 
  478. $group_id = bp_get_current_group_id(); 
  479.  
  480. // On the admin, get the group id out of the $_GET params. 
  481. if ( empty( $group_id ) && is_admin() && ( isset( $_GET['page'] ) && ( 'bp-groups' === $_GET['page'] ) ) && ! empty( $_GET['gid'] ) ) { 
  482. $group_id = (int) $_GET['gid']; 
  483.  
  484. // This fallback will only be hit when the create step is very 
  485. // early. 
  486. if ( empty( $group_id ) && bp_get_new_group_id() ) { 
  487. $group_id = bp_get_new_group_id(); 
  488.  
  489. // On some setups, the group id has to be fetched out of the 
  490. // $_POST array 
  491. // @todo Figure out why this is happening during group creation. 
  492. if ( empty( $group_id ) && isset( $_POST['group_id'] ) ) { 
  493. $group_id = (int) $_POST['group_id']; 
  494.  
  495. return $group_id; 
  496.  
  497. /** 
  498. * Gather configuration data about your screens. 
  499. * 
  500. * @since 1.8.0 
  501. * 
  502. * @return array 
  503. */ 
  504. protected function get_default_screens() { 
  505. $this->setup_class_info(); 
  506.  
  507. $screens = array( 
  508. 'create' => array( 
  509. 'position' => 81,  
  510. ),  
  511. 'edit' => array( 
  512. 'submit_text' => __( 'Save Changes', 'buddypress' ),  
  513. ),  
  514. 'admin' => array( 
  515. 'metabox_context' => 'normal',  
  516. 'metabox_priority' => 'core',  
  517. ),  
  518. ); 
  519.  
  520. foreach ( $screens as $context => &$screen ) { 
  521. $screen['enabled'] = true; 
  522. $screen['name'] = $this->name; 
  523. $screen['slug'] = $this->slug; 
  524.  
  525. $screen['screen_callback'] = $this->get_screen_callback( $context, 'screen' ); 
  526. $screen['screen_save_callback'] = $this->get_screen_callback( $context, 'screen_save' ); 
  527.  
  528. return $screens; 
  529.  
  530. /** 
  531. * Set up screens array based on params. 
  532. * 
  533. * @since 1.8.0 
  534. */ 
  535. protected function setup_screens() { 
  536. foreach ( (array) $this->params['screens'] as $context => $screen ) { 
  537. if ( empty( $screen['slug'] ) ) { 
  538. $screen['slug'] = $this->slug; 
  539.  
  540. if ( empty( $screen['name'] ) ) { 
  541. $screen['name'] = $this->name; 
  542.  
  543. $this->screens[ $context ] = $screen; 
  544.  
  545. /** 
  546. * Set up access-related settings for this extension. 
  547. * 
  548. * @since 2.1.0 
  549. */ 
  550. protected function setup_access_settings() { 
  551. // Bail if no group ID is available. 
  552. if ( empty( $this->group_id ) ) { 
  553. return; 
  554.  
  555. // Backward compatibility. 
  556. if ( isset( $this->params['enable_nav_item'] ) ) { 
  557. $this->enable_nav_item = (bool) $this->params['enable_nav_item']; 
  558.  
  559. // Tab Access. 
  560. $this->user_can_visit = false; 
  561.  
  562. // Backward compatibility for components that do not provide 
  563. // explicit 'access' parameter. 
  564. if ( empty( $this->params['access'] ) ) { 
  565. if ( false === $this->enable_nav_item ) { 
  566. $this->params['access'] = 'noone'; 
  567. } else { 
  568. $group = groups_get_group( $this->group_id ); 
  569.  
  570. if ( ! empty( $group->status ) && 'public' === $group->status ) { 
  571. // Tabs in public groups are accessible to anyone by default. 
  572. $this->params['access'] = 'anyone'; 
  573. } else { 
  574. // All other groups have members-only as the default. 
  575. $this->params['access'] = 'member'; 
  576.  
  577. // Parse multiple access conditions into an array. 
  578. $access_conditions = $this->params['access']; 
  579. if ( ! is_array( $access_conditions ) ) { 
  580. $access_conditions = explode( ', ', $access_conditions ); 
  581.  
  582. // If the current user meets at least one condition, the 
  583. // get access. 
  584. foreach ( $access_conditions as $access_condition ) { 
  585. if ( $this->user_meets_access_condition( $access_condition ) ) { 
  586. $this->user_can_visit = true; 
  587. break; 
  588.  
  589. // Tab Visibility. 
  590. $this->user_can_see_nav_item = false; 
  591.  
  592. // Backward compatibility for components that do not provide 
  593. // explicit 'show_tab' parameter. 
  594. if ( empty( $this->params['show_tab'] ) ) { 
  595. if ( false === $this->params['enable_nav_item'] ) { 
  596. // The enable_nav_item index is only false if it's been 
  597. // defined explicitly as such in the 
  598. // constructor. So we always trust this value. 
  599. $this->params['show_tab'] = 'noone'; 
  600.  
  601. } elseif ( isset( $this->params_raw['enable_nav_item'] ) || isset( $this->params_raw['visibility'] ) ) { 
  602. // If enable_nav_item or visibility is passed,  
  603. // we assume this is a legacy extension. 
  604. // Legacy behavior is that enable_nav_item=true + 
  605. // visibility=private implies members-only. 
  606. if ( 'public' !== $this->visibility ) { 
  607. $this->params['show_tab'] = 'member'; 
  608. } else { 
  609. $this->params['show_tab'] = 'anyone'; 
  610.  
  611. } else { 
  612. // No show_tab or enable_nav_item value is 
  613. // available, so match the value of 'access'. 
  614. $this->params['show_tab'] = $this->params['access']; 
  615.  
  616. // Parse multiple access conditions into an array. 
  617. $access_conditions = $this->params['show_tab']; 
  618. if ( ! is_array( $access_conditions ) ) { 
  619. $access_conditions = explode( ', ', $access_conditions ); 
  620.  
  621. // If the current user meets at least one condition, the 
  622. // get access. 
  623. foreach ( $access_conditions as $access_condition ) { 
  624. if ( $this->user_meets_access_condition( $access_condition ) ) { 
  625. $this->user_can_see_nav_item = true; 
  626. break; 
  627.  
  628. /** 
  629. * Check whether the current user meets an access condition. 
  630. * 
  631. * @since 2.1.0 
  632. * 
  633. * @param string $access_condition 'anyone', 'loggedin', 'member',  
  634. * 'mod', 'admin' or 'noone'. 
  635. * @return bool 
  636. */ 
  637. protected function user_meets_access_condition( $access_condition ) { 
  638. $group = groups_get_group( $this->group_id ); 
  639.  
  640. switch ( $access_condition ) { 
  641. case 'admin' : 
  642. $meets_condition = groups_is_user_admin( bp_loggedin_user_id(), $this->group_id ); 
  643. break; 
  644.  
  645. case 'mod' : 
  646. $meets_condition = groups_is_user_mod( bp_loggedin_user_id(), $this->group_id ); 
  647. break; 
  648.  
  649. case 'member' : 
  650. $meets_condition = groups_is_user_member( bp_loggedin_user_id(), $this->group_id ); 
  651. break; 
  652.  
  653. case 'loggedin' : 
  654. $meets_condition = is_user_logged_in(); 
  655. break; 
  656.  
  657. case 'noone' : 
  658. $meets_condition = false; 
  659. break; 
  660.  
  661. case 'anyone' : 
  662. default : 
  663. $meets_condition = true; 
  664. break; 
  665.  
  666. return $meets_condition; 
  667.  
  668. /** Display ***********************************************************/ 
  669.  
  670. /** 
  671. * Hook this extension's group tab into BuddyPress, if necessary. 
  672. * 
  673. * @since 1.8.0 
  674. */ 
  675. protected function setup_display_hooks() { 
  676.  
  677. // Bail if not a group. 
  678. if ( ! bp_is_group() ) { 
  679. return; 
  680.  
  681. // Backward compatibility only. 
  682. if ( ( 'public' !== $this->visibility ) && ! buddypress()->groups->current_group->user_has_access ) { 
  683. return; 
  684.  
  685. // If the user can see the nav item, we create it. 
  686. $user_can_see_nav_item = $this->user_can_see_nav_item(); 
  687.  
  688. if ( $user_can_see_nav_item ) { 
  689. $group_permalink = bp_get_group_permalink( groups_get_current_group() ); 
  690.  
  691. bp_core_create_subnav_link( array( 
  692. 'name' => ! $this->nav_item_name ? $this->name : $this->nav_item_name,  
  693. 'slug' => $this->slug,  
  694. 'parent_slug' => bp_get_current_group_slug(),  
  695. 'parent_url' => $group_permalink,  
  696. 'position' => $this->nav_item_position,  
  697. 'item_css_id' => 'nav-' . $this->slug,  
  698. 'screen_function' => array( &$this, '_display_hook' ),  
  699. 'user_has_access' => $user_can_see_nav_item,  
  700. 'no_access_url' => $group_permalink,  
  701. ), 'groups' ); 
  702.  
  703. // If the user can visit the screen, we register it. 
  704. $user_can_visit = $this->user_can_visit(); 
  705.  
  706. if ( $user_can_visit ) { 
  707. $group_permalink = bp_get_group_permalink( groups_get_current_group() ); 
  708.  
  709. bp_core_register_subnav_screen_function( array( 
  710. 'slug' => $this->slug,  
  711. 'parent_slug' => bp_get_current_group_slug(),  
  712. 'screen_function' => array( &$this, '_display_hook' ),  
  713. 'user_has_access' => $user_can_visit,  
  714. 'no_access_url' => $group_permalink,  
  715. ), 'groups' ); 
  716.  
  717. // When we are viewing the extension display page, set the title and options title. 
  718. if ( bp_is_current_action( $this->slug ) ) { 
  719. add_filter( 'bp_group_user_has_access', array( $this, 'group_access_protection' ), 10, 2 ); 
  720. add_action( 'bp_template_content_header', create_function( '', 'echo "' . esc_attr( $this->name ) . '";' ) ); 
  721. add_action( 'bp_template_title', create_function( '', 'echo "' . esc_attr( $this->name ) . '";' ) ); 
  722.  
  723. // Hook the group home widget. 
  724. if ( bp_is_group_home() ) { 
  725. add_action( $this->display_hook, array( &$this, 'widget_display' ) ); 
  726.  
  727. /** 
  728. * Hook the main display method, and loads the template file. 
  729. * 
  730. * @since 1.1.0 
  731. */ 
  732. public function _display_hook() { 
  733. add_action( 'bp_template_content', array( &$this, 'call_display' ) ); 
  734.  
  735. /** 
  736. * Filters the template to load for the main display method. 
  737. * 
  738. * @since 1.0.0 
  739. * 
  740. * @param string $template_file Path to the template to load. 
  741. */ 
  742. bp_core_load_template( apply_filters( 'bp_core_template_plugin', $this->template_file ) ); 
  743.  
  744. /** 
  745. * Call the display() method. 
  746. * 
  747. * We use this wrapper so that we can pass the group_id to the 
  748. * display() callback. 
  749. * 
  750. * @since 2.1.1 
  751. */ 
  752. public function call_display() { 
  753. $this->display( $this->group_id ); 
  754.  
  755. /** 
  756. * Determine whether the current user should see this nav tab. 
  757. * 
  758. * Note that this controls only the display of the navigation item. 
  759. * Access to the tab is controlled by the user_can_visit() check. 
  760. * 
  761. * @since 2.1.0 
  762. * 
  763. * @param bool $user_can_see_nav_item Whether or not the user can see the nav item. 
  764. * @return bool 
  765. */ 
  766. public function user_can_see_nav_item( $user_can_see_nav_item = false ) { 
  767. if ( 'noone' !== $this->params['show_tab'] && current_user_can( 'bp_moderate' ) ) { 
  768. return true; 
  769.  
  770. return $this->user_can_see_nav_item; 
  771.  
  772. /** 
  773. * Determine whether the current user has access to visit this tab. 
  774. * 
  775. * @since 2.1.0 
  776. * 
  777. * @param bool $user_can_visit Whether or not the user can visit the tab. 
  778. * @return bool 
  779. */ 
  780. public function user_can_visit( $user_can_visit = false ) { 
  781. if ( 'noone' !== $this->params['access'] && current_user_can( 'bp_moderate' ) ) { 
  782. return true; 
  783.  
  784. return $this->user_can_visit; 
  785.  
  786. /** 
  787. * Filter the access check in bp_groups_group_access_protection() for this extension. 
  788. * 
  789. * Note that $no_access_args is passed by reference, as there are some 
  790. * circumstances where the bp_core_no_access() arguments need to be 
  791. * modified before the redirect takes place. 
  792. * 
  793. * @since 2.1.0 
  794. * 
  795. * @param bool $user_can_visit Whether or not the user can visit the tab. 
  796. * @param array $no_access_args Array of args to help determine access. 
  797. * @return bool 
  798. */ 
  799. public function group_access_protection( $user_can_visit, &$no_access_args ) { 
  800. $user_can_visit = $this->user_can_visit(); 
  801.  
  802. if ( ! $user_can_visit && is_user_logged_in() ) { 
  803. $current_group = groups_get_group( $this->group_id ); 
  804.  
  805. $no_access_args['message'] = __( 'You do not have access to this content.', 'buddypress' ); 
  806. $no_access_args['root'] = bp_get_group_permalink( $current_group ) . 'home/'; 
  807. $no_access_args['redirect'] = false; 
  808.  
  809. return $user_can_visit; 
  810.  
  811.  
  812. /** Create ************************************************************/ 
  813.  
  814. /** 
  815. * Hook this extension's Create step into BuddyPress, if necessary. 
  816. * 
  817. * @since 1.8.0 
  818. */ 
  819. protected function setup_create_hooks() { 
  820. if ( ! $this->is_screen_enabled( 'create' ) ) { 
  821. return; 
  822.  
  823. $screen = $this->screens['create']; 
  824.  
  825. // Insert the group creation step for the new group extension. 
  826. buddypress()->groups->group_creation_steps[ $screen['slug'] ] = array( 
  827. 'name' => $screen['name'],  
  828. 'slug' => $screen['slug'],  
  829. 'position' => $screen['position'],  
  830. ); 
  831.  
  832. // The maybe_ methods check to see whether the create_* 
  833. // callbacks should be invoked (ie, are we on the 
  834. // correct group creation step). Hooked in separate 
  835. // methods because current creation step info not yet 
  836. // available at this point. 
  837. add_action( 'groups_custom_create_steps', array( $this, 'maybe_create_screen' ) ); 
  838. add_action( 'groups_create_group_step_save_' . $screen['slug'], array( $this, 'maybe_create_screen_save' ) ); 
  839.  
  840. /** 
  841. * Call the create_screen() method, if we're on the right page. 
  842. * 
  843. * @since 1.8.0 
  844. */ 
  845. public function maybe_create_screen() { 
  846. if ( ! bp_is_group_creation_step( $this->screens['create']['slug'] ) ) { 
  847. return; 
  848.  
  849. call_user_func( $this->screens['create']['screen_callback'], $this->group_id ); 
  850. $this->nonce_field( 'create' ); 
  851.  
  852. // The create screen requires an additional nonce field 
  853. // due to a quirk in the way the templates are built. 
  854. wp_nonce_field( 'groups_create_save_' . bp_get_groups_current_create_step(), '_wpnonce', false ); 
  855.  
  856. /** 
  857. * Call the create_screen_save() method, if we're on the right page. 
  858. * 
  859. * @since 1.8.0 
  860. */ 
  861. public function maybe_create_screen_save() { 
  862. if ( ! bp_is_group_creation_step( $this->screens['create']['slug'] ) ) { 
  863. return; 
  864.  
  865. $this->check_nonce( 'create' ); 
  866. call_user_func( $this->screens['create']['screen_save_callback'], $this->group_id ); 
  867.  
  868. /** Edit **************************************************************/ 
  869.  
  870. /** 
  871. * Hook this extension's Edit panel into BuddyPress, if necessary. 
  872. * 
  873. * @since 1.8.0 
  874. */ 
  875. protected function setup_edit_hooks() { 
  876. // Bail if not in a group. 
  877. if ( ! bp_is_group() ) { 
  878. return; 
  879.  
  880. // Bail if not an edit screen. 
  881. if ( ! $this->is_screen_enabled( 'edit' ) || ! bp_is_item_admin() ) { 
  882. return; 
  883.  
  884. $screen = $this->screens['edit']; 
  885.  
  886. $position = isset( $screen['position'] ) ? (int) $screen['position'] : 10; 
  887. $position += 40; 
  888.  
  889. $current_group = groups_get_current_group(); 
  890. $admin_link = trailingslashit( bp_get_group_permalink( $current_group ) . 'admin' ); 
  891.  
  892. $subnav_args = array( 
  893. 'name' => $screen['name'],  
  894. 'slug' => $screen['slug'],  
  895. 'parent_slug' => $current_group->slug . '_manage',  
  896. 'parent_url' => trailingslashit( bp_get_group_permalink( $current_group ) . 'admin' ),  
  897. 'user_has_access' => bp_is_item_admin(),  
  898. 'position' => $position,  
  899. 'screen_function' => 'groups_screen_group_admin',  
  900. ); 
  901.  
  902. // Should we add a menu to the Group's WP Admin Bar. 
  903. if ( ! empty( $screen['show_in_admin_bar'] ) ) { 
  904. $subnav_args['show_in_admin_bar'] = true; 
  905.  
  906. // Add the tab to the manage navigation. 
  907. bp_core_new_subnav_item( $subnav_args, 'groups' ); 
  908.  
  909. // Catch the edit screen and forward it to the plugin template. 
  910. if ( bp_is_groups_component() && bp_is_current_action( 'admin' ) && bp_is_action_variable( $screen['slug'], 0 ) ) { 
  911. $this->call_edit_screen_save( $this->group_id ); 
  912.  
  913. add_action( 'groups_custom_edit_steps', array( &$this, 'call_edit_screen' ) ); 
  914.  
  915. // Determine the proper template and save for later 
  916. // loading. 
  917. if ( '' !== bp_locate_template( array( 'groups/single/home.php' ), false ) ) { 
  918. $this->edit_screen_template = '/groups/single/home'; 
  919. } else { 
  920. add_action( 'bp_template_content_header', function() { 
  921. echo '<ul class="content-header-nav">'; 
  922. bp_group_admin_tabs(); 
  923. echo '</ul>'; 
  924. } ); 
  925. add_action( 'bp_template_content', array( &$this, 'call_edit_screen' ) ); 
  926. $this->edit_screen_template = '/groups/single/plugins'; 
  927.  
  928. // We load the template at bp_screens, to give all 
  929. // extensions a chance to load. 
  930. add_action( 'bp_screens', array( $this, 'call_edit_screen_template_loader' ) ); 
  931.  
  932. /** 
  933. * Call the edit_screen() method. 
  934. * 
  935. * Previous versions of BP_Group_Extension required plugins to provide 
  936. * their own Submit button and nonce fields when building markup. In 
  937. * BP 1.8, this requirement was lifted - BP_Group_Extension now handles 
  938. * all required submit buttons and nonces. 
  939. * 
  940. * We put the edit screen markup into an output buffer before echoing. 
  941. * This is so that we can check for the presence of a hardcoded submit 
  942. * button, as would be present in legacy plugins; if one is found, we 
  943. * do not auto-add our own button. 
  944. * 
  945. * @since 1.8.0 
  946. */ 
  947. public function call_edit_screen() { 
  948. ob_start(); 
  949. call_user_func( $this->screens['edit']['screen_callback'], $this->group_id ); 
  950. $screen = ob_get_contents(); 
  951. ob_end_clean(); 
  952.  
  953. echo $this->maybe_add_submit_button( $screen ); 
  954.  
  955. $this->nonce_field( 'edit' ); 
  956.  
  957. /** 
  958. * Check the nonce, and call the edit_screen_save() method. 
  959. * 
  960. * @since 1.8.0 
  961. */ 
  962. public function call_edit_screen_save() { 
  963. if ( empty( $_POST ) ) { 
  964. return; 
  965.  
  966. // When DOING_AJAX, the POST global will be populated, but we 
  967. // should assume it's a save. 
  968. if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { 
  969. return; 
  970.  
  971. $this->check_nonce( 'edit' ); 
  972.  
  973. // Detect whether the screen_save_callback is performing a 
  974. // redirect, so that we don't do one of our own. 
  975. add_filter( 'wp_redirect', array( $this, 'detect_post_save_redirect' ) ); 
  976.  
  977. // Call the extension's save routine. 
  978. call_user_func( $this->screens['edit']['screen_save_callback'], $this->group_id ); 
  979.  
  980. // Clean up detection filters. 
  981. remove_filter( 'wp_redirect', array( $this, 'detect_post_save_redirect' ) ); 
  982.  
  983. // Perform a redirect only if one has not already taken place. 
  984. if ( empty( $this->post_save_redirect ) ) { 
  985.  
  986. /** 
  987. * Filters the URL to redirect to after group edit screen save. 
  988. * 
  989. * Only runs if a redirect has not already occurred. 
  990. * 
  991. * @since 2.1.0 
  992. * 
  993. * @param string $value URL to redirect to. 
  994. */ 
  995. $redirect_to = apply_filters( 'bp_group_extension_edit_screen_save_redirect', bp_get_requested_url( ) ); 
  996.  
  997. bp_core_redirect( $redirect_to ); 
  998. die(); 
  999.  
  1000. /** 
  1001. * Load the template that houses the Edit screen. 
  1002. * 
  1003. * Separated out into a callback so that it can run after all other 
  1004. * Group Extensions have had a chance to register their navigation, to 
  1005. * avoid missing tabs. 
  1006. * 
  1007. * Hooked to 'bp_screens'. 
  1008. * 
  1009. * @since 1.8.0 
  1010. * 
  1011. * @see BP_Group_Extension::setup_edit_hooks() 
  1012. */ 
  1013. public function call_edit_screen_template_loader() { 
  1014. bp_core_load_template( $this->edit_screen_template ); 
  1015.  
  1016. /** 
  1017. * Add a submit button to the edit form, if it needs one. 
  1018. * 
  1019. * There's an inconsistency in the way that the group Edit and Create 
  1020. * screens are rendered: the Create screen has a submit button built 
  1021. * in, but the Edit screen does not. This function allows plugin 
  1022. * authors to write markup that does not contain the submit button for 
  1023. * use on both the Create and Edit screens - BP will provide the button 
  1024. * if one is not found. 
  1025. * 
  1026. * @since 1.8.0 
  1027. * 
  1028. * @param string $screen The screen markup, captured in the output 
  1029. * buffer. 
  1030. * @return string $screen The same markup, with a submit button added. 
  1031. */ 
  1032. protected function maybe_add_submit_button( $screen = '' ) { 
  1033. if ( $this->has_submit_button( $screen ) ) { 
  1034. return $screen; 
  1035.  
  1036. return $screen . sprintf( 
  1037. '<div id="%s"><input type="submit" name="save" value="%s" id="%s"></div>',  
  1038. 'bp-group-edit-' . $this->slug . '-submit-wrapper',  
  1039. $this->screens['edit']['submit_text'],  
  1040. 'bp-group-edit-' . $this->slug . '-submit' 
  1041. ); 
  1042.  
  1043. /** 
  1044. * Does the given markup have a submit button? 
  1045. * 
  1046. * @since 1.8.0 
  1047. * 
  1048. * @param string $screen The markup to check. 
  1049. * @return bool True if a Submit button is found, otherwise false. 
  1050. */ 
  1051. public static function has_submit_button( $screen = '' ) { 
  1052. $pattern = "/<input[^>]+type=[\'\"]submit[\'\"]/"; 
  1053. preg_match( $pattern, $screen, $matches ); 
  1054. return ! empty( $matches[0] ); 
  1055.  
  1056. /** 
  1057. * Detect redirects hardcoded into edit_screen_save() callbacks. 
  1058. * 
  1059. * @since 2.1.0 
  1060. * 
  1061. * @param string $redirect Redirect string. 
  1062. * @return string 
  1063. */ 
  1064. public function detect_post_save_redirect( $redirect = '' ) { 
  1065. if ( ! empty( $redirect ) ) { 
  1066. $this->post_save_redirect = $redirect; 
  1067.  
  1068. return $redirect; 
  1069.  
  1070. /** Admin *************************************************************/ 
  1071.  
  1072. /** 
  1073. * Hook this extension's Admin metabox into BuddyPress, if necessary. 
  1074. * 
  1075. * @since 1.8.0 
  1076. */ 
  1077. protected function setup_admin_hooks() { 
  1078. if ( ! $this->is_screen_enabled( 'admin' ) || ! is_admin() ) { 
  1079. return; 
  1080.  
  1081. // Hook the admin screen markup function to the content hook. 
  1082. add_action( 'bp_groups_admin_meta_box_content_' . $this->slug, array( $this, 'call_admin_screen' ) ); 
  1083.  
  1084. // Initialize the metabox. 
  1085. add_action( 'bp_groups_admin_meta_boxes', array( $this, '_meta_box_display_callback' ) ); 
  1086.  
  1087. // Catch the metabox save. 
  1088. add_action( 'bp_group_admin_edit_after', array( $this, 'call_admin_screen_save' ), 10 ); 
  1089.  
  1090. /** 
  1091. * Call the admin_screen() method, and add a nonce field. 
  1092. * 
  1093. * @since 1.8.0 
  1094. */ 
  1095. public function call_admin_screen() { 
  1096. call_user_func( $this->screens['admin']['screen_callback'], $this->group_id ); 
  1097. $this->nonce_field( 'admin' ); 
  1098.  
  1099. /** 
  1100. * Check the nonce, and call the admin_screen_save() method. 
  1101. * 
  1102. * @since 1.8.0 
  1103. */ 
  1104. public function call_admin_screen_save() { 
  1105. $this->check_nonce( 'admin' ); 
  1106. call_user_func( $this->screens['admin']['screen_save_callback'], $this->group_id ); 
  1107.  
  1108. /** 
  1109. * Create the Dashboard meta box for this extension. 
  1110. * 
  1111. * @since 1.7.0 
  1112. */ 
  1113. public function _meta_box_display_callback() { 
  1114. $group_id = isset( $_GET['gid'] ) ? (int) $_GET['gid'] : 0; 
  1115. $screen = $this->screens['admin']; 
  1116.  
  1117. add_meta_box( 
  1118. $screen['slug'],  
  1119. $screen['name'],  
  1120. create_function( '', 'do_action( "bp_groups_admin_meta_box_content_' . $this->slug . '", ' . $group_id . ' );' ),  
  1121. get_current_screen()->id,  
  1122. $screen['metabox_context'],  
  1123. $screen['metabox_priority'] 
  1124. ); 
  1125.  
  1126.  
  1127. /** Utilities *********************************************************/ 
  1128.  
  1129. /** 
  1130. * Generate the nonce fields for a settings form. 
  1131. * 
  1132. * The nonce field name (the second param passed to wp_nonce_field) 
  1133. * contains this extension's slug and is thus unique to this extension. 
  1134. * This is necessary because in some cases (namely, the Dashboard),  
  1135. * more than one extension may generate nonces on the same page, and we 
  1136. * must avoid name clashes. 
  1137. * 
  1138. * @since 1.8.0 
  1139. * 
  1140. * @param string $context Screen context. 'create', 'edit', or 'admin'. 
  1141. */ 
  1142. public function nonce_field( $context = '' ) { 
  1143. wp_nonce_field( 'bp_group_extension_' . $this->slug . '_' . $context, '_bp_group_' . $context . '_nonce_' . $this->slug ); 
  1144.  
  1145. /** 
  1146. * Check the nonce on a submitted settings form. 
  1147. * 
  1148. * @since 1.8.0 
  1149. * 
  1150. * @param string $context Screen context. 'create', 'edit', or 'admin'. 
  1151. */ 
  1152. public function check_nonce( $context = '' ) { 
  1153. check_admin_referer( 'bp_group_extension_' . $this->slug . '_' . $context, '_bp_group_' . $context . '_nonce_' . $this->slug ); 
  1154.  
  1155. /** 
  1156. * Is the specified screen enabled? 
  1157. * 
  1158. * To be enabled, a screen must both have the 'enabled' key set to true 
  1159. * (legacy: $this->enable_create_step, etc), and its screen_callback 
  1160. * must also exist and be callable. 
  1161. * 
  1162. * @since 1.8.0 
  1163. * 
  1164. * @param string $context Screen context. 'create', 'edit', or 'admin'. 
  1165. * @return bool True if the screen is enabled, otherwise false. 
  1166. */ 
  1167. public function is_screen_enabled( $context = '' ) { 
  1168. $enabled = false; 
  1169.  
  1170. if ( isset( $this->screens[ $context ] ) ) { 
  1171. $enabled = $this->screens[ $context ]['enabled'] && is_callable( $this->screens[ $context ]['screen_callback'] ); 
  1172.  
  1173. return (bool) $enabled; 
  1174.  
  1175. /** 
  1176. * Get the appropriate screen callback for the specified context/type. 
  1177. * 
  1178. * BP Group Extensions have three special "screen contexts": create,  
  1179. * admin, and edit. Each of these contexts has a corresponding 
  1180. * _screen() and _screen_save() method, which allow group extension 
  1181. * plugins to define different markup and logic for each context. 
  1182. * 
  1183. * BP also supports fallback settings_screen() and 
  1184. * settings_screen_save() methods, which can be used to define markup 
  1185. * and logic that is shared between context. For each context, you may 
  1186. * either provide context-specific methods, or you can let BP fall back 
  1187. * on the shared settings_* callbacks. 
  1188. * 
  1189. * For example, consider a BP_Group_Extension implementation that looks 
  1190. * like this: 
  1191. * 
  1192. * // ... 
  1193. * function create_screen( $group_id ) { ... } 
  1194. * function create_screen_save( $group_id ) { ... } 
  1195. * function settings_screen( $group_id ) { ... } 
  1196. * function settings_screen_save( $group_id ) { ... } 
  1197. * // ... 
  1198. * 
  1199. * BP_Group_Extension will use your create_* methods for the Create 
  1200. * steps, and will use your generic settings_* methods for the Edit 
  1201. * and Admin contexts. This schema allows plugin authors maximum 
  1202. * flexibility without having to repeat themselves. 
  1203. * 
  1204. * The get_screen_callback() method uses a ReflectionClass object to 
  1205. * determine whether your extension has provided a given callback. 
  1206. * 
  1207. * @since 1.8.0 
  1208. * 
  1209. * @param string $context Screen context. 'create', 'edit', or 'admin'. 
  1210. * @param string $type Screen type. 'screen' or 'screen_save'. Default: 
  1211. * 'screen'. 
  1212. * @return callable A callable function handle. 
  1213. */ 
  1214. public function get_screen_callback( $context = '', $type = 'screen' ) { 
  1215. $callback = ''; 
  1216.  
  1217. // Try the context-specific callback first. 
  1218. $method = $context . '_' . $type; 
  1219. $rmethod = $this->class_reflection->getMethod( $method ); 
  1220. if ( isset( $rmethod->class ) && $this->class_name === $rmethod->class ) { 
  1221. $callback = array( $this, $method ); 
  1222.  
  1223. if ( empty( $callback ) ) { 
  1224. $fallback_method = 'settings_' . $type; 
  1225. $rfallback_method = $this->class_reflection->getMethod( $fallback_method ); 
  1226. if ( isset( $rfallback_method->class ) && $this->class_name === $rfallback_method->class ) { 
  1227. $callback = array( $this, $fallback_method ); 
  1228.  
  1229. return $callback; 
  1230.  
  1231. /** 
  1232. * Recursive argument parsing. 
  1233. * 
  1234. * This acts like a multi-dimensional version of wp_parse_args() (minus 
  1235. * the querystring parsing - you must pass arrays). 
  1236. * 
  1237. * Values from $a override those from $b; keys in $b that don't exist 
  1238. * in $a are passed through. 
  1239. * 
  1240. * This is different from array_merge_recursive(), both because of the 
  1241. * order of preference ($a overrides $b) and because of the fact that 
  1242. * array_merge_recursive() combines arrays deep in the tree, rather 
  1243. * than overwriting the b array with the a array. 
  1244. * 
  1245. * The implementation of this function is specific to the needs of 
  1246. * BP_Group_Extension, where we know that arrays will always be 
  1247. * associative, and that an argument under a given key in one array 
  1248. * will be matched by a value of identical depth in the other one. The 
  1249. * function is NOT designed for general use, and will probably result 
  1250. * in unexpected results when used with data in the wild. See, eg,  
  1251. * https://core.trac.wordpress.org/ticket/19888 
  1252. * 
  1253. * @since 1.8.0 
  1254. * 
  1255. * @param array $a First set of arguments. 
  1256. * @param array $b Second set of arguments. 
  1257. * @return array Parsed arguments. 
  1258. */ 
  1259. public static function parse_args_r( &$a, $b ) { 
  1260. $a = (array) $a; 
  1261. $b = (array) $b; 
  1262. $r = $b; 
  1263.  
  1264. foreach ( $a as $k => &$v ) { 
  1265. if ( is_array( $v ) && isset( $r[ $k ] ) ) { 
  1266. $r[ $k ] = self::parse_args_r( $v, $r[ $k ] ); 
  1267. } else { 
  1268. $r[ $k ] = $v; 
  1269.  
  1270. return $r; 
  1271.  
  1272. /** Legacy Support ********************************************************/ 
  1273.  
  1274. /** 
  1275. * In BuddyPress 1.8, the recommended technique for configuring 
  1276. * extensions changed from directly setting various object properties 
  1277. * in the class constructor, to passing a configuration array to 
  1278. * parent::init(). The following methods ensure that extensions created 
  1279. * in the old way continue to work, by converting legacy configuration 
  1280. * data to the new format. 
  1281. */ 
  1282.  
  1283. /** 
  1284. * Provide access to otherwise unavailable object properties. 
  1285. * 
  1286. * This magic method is here for backward compatibility with plugins 
  1287. * that refer to config properties that have moved to a different 
  1288. * location (such as enable_create_step, which is now at 
  1289. * $this->screens['create']['enabled'] 
  1290. * 
  1291. * The legacy_properties array is set up in 
  1292. * self::setup_legacy_properties(). 
  1293. * 
  1294. * @since 1.8.0 
  1295. * 
  1296. * @param string $key Property name. 
  1297. * @return mixed The value if found, otherwise null. 
  1298. */ 
  1299. public function __get( $key ) { 
  1300. if ( isset( $this->legacy_properties[ $key ] ) ) { 
  1301. return $this->legacy_properties[ $key ]; 
  1302. } elseif ( isset( $this->data[ $key ] ) ) { 
  1303. return $this->data[ $key ]; 
  1304. } else { 
  1305. return null; 
  1306.  
  1307. /** 
  1308. * Provide a fallback for isset( $this->foo ) when foo is unavailable. 
  1309. * 
  1310. * This magic method is here for backward compatibility with plugins 
  1311. * that have set their class config options directly in the class 
  1312. * constructor. The parse_legacy_properties() method of the current 
  1313. * class needs to check whether any legacy keys have been put into the 
  1314. * $this->data array. 
  1315. * 
  1316. * @since 1.8.0 
  1317. * 
  1318. * @param string $key Property name. 
  1319. * @return bool True if the value is set, otherwise false. 
  1320. */ 
  1321. public function __isset( $key ) { 
  1322. if ( isset( $this->legacy_properties[ $key ] ) ) { 
  1323. return true; 
  1324. } elseif ( isset( $this->data[ $key ] ) ) { 
  1325. return true; 
  1326. } else { 
  1327. return false; 
  1328.  
  1329. /** 
  1330. * Allow plugins to set otherwise unavailable object properties. 
  1331. * 
  1332. * This magic method is here for backward compatibility with plugins 
  1333. * that may attempt to modify the group extension by manually assigning 
  1334. * a value to an object property that no longer exists, such as 
  1335. * $this->enable_create_step. 
  1336. * 
  1337. * @since 1.8.0 
  1338. * 
  1339. * @param string $key Property name. 
  1340. * @param mixed $value Property value. 
  1341. */ 
  1342. public function __set( $key, $value ) { 
  1343.  
  1344. if ( empty( $this->initialized ) ) { 
  1345. $this->data[ $key ] = $value; 
  1346.  
  1347. switch ( $key ) { 
  1348. case 'enable_create_step' : 
  1349. $this->screens['create']['enabled'] = $value; 
  1350. break; 
  1351.  
  1352. case 'enable_edit_item' : 
  1353. $this->screens['edit']['enabled'] = $value; 
  1354. break; 
  1355.  
  1356. case 'enable_admin_item' : 
  1357. $this->screens['admin']['enabled'] = $value; 
  1358. break; 
  1359.  
  1360. case 'create_step_position' : 
  1361. $this->screens['create']['position'] = $value; 
  1362. break; 
  1363.  
  1364. // Note: 'admin' becomes 'edit' to distinguish from Dashboard 'admin'. 
  1365. case 'admin_name' : 
  1366. $this->screens['edit']['name'] = $value; 
  1367. break; 
  1368.  
  1369. case 'admin_slug' : 
  1370. $this->screens['edit']['slug'] = $value; 
  1371. break; 
  1372.  
  1373. case 'create_name' : 
  1374. $this->screens['create']['name'] = $value; 
  1375. break; 
  1376.  
  1377. case 'create_slug' : 
  1378. $this->screens['create']['slug'] = $value; 
  1379. break; 
  1380.  
  1381. case 'admin_metabox_context' : 
  1382. $this->screens['admin']['metabox_context'] = $value; 
  1383. break; 
  1384.  
  1385. case 'admin_metabox_priority' : 
  1386. $this->screens['admin']['metabox_priority'] = $value; 
  1387. break; 
  1388.  
  1389. default : 
  1390. $this->data[ $key ] = $value; 
  1391. break; 
  1392.  
  1393. /** 
  1394. * Return a list of legacy properties. 
  1395. * 
  1396. * The legacy implementation of BP_Group_Extension used all of these 
  1397. * object properties for configuration. Some have been moved. 
  1398. * 
  1399. * @since 1.8.0 
  1400. * 
  1401. * @return array List of legacy property keys. 
  1402. */ 
  1403. protected function get_legacy_property_list() { 
  1404. return array( 
  1405. 'name',  
  1406. 'slug',  
  1407. 'admin_name',  
  1408. 'admin_slug',  
  1409. 'create_name',  
  1410. 'create_slug',  
  1411. 'visibility',  
  1412. 'create_step_position',  
  1413. 'nav_item_position',  
  1414. 'admin_metabox_context',  
  1415. 'admin_metabox_priority',  
  1416. 'enable_create_step',  
  1417. 'enable_nav_item',  
  1418. 'enable_edit_item',  
  1419. 'enable_admin_item',  
  1420. 'nav_item_name',  
  1421. 'display_hook',  
  1422. 'template_file',  
  1423. ); 
  1424.  
  1425. /** 
  1426. * Parse legacy properties. 
  1427. * 
  1428. * The old standard for BP_Group_Extension was for plugins to register 
  1429. * their settings as properties in their constructor. The new method is 
  1430. * to pass a config array to the init() method. In order to support 
  1431. * legacy plugins, we slurp up legacy properties, and later on we'll 
  1432. * parse them into the new init() array. 
  1433. * 
  1434. * @since 1.8.0 
  1435. */ 
  1436. protected function parse_legacy_properties() { 
  1437.  
  1438. // Only run this one time. 
  1439. if ( ! empty( $this->legacy_properties_converted ) ) { 
  1440. return; 
  1441.  
  1442. $properties = $this->get_legacy_property_list(); 
  1443.  
  1444. // By-reference variable for convenience. 
  1445. $lpc =& $this->legacy_properties_converted; 
  1446.  
  1447. foreach ( $properties as $property ) { 
  1448.  
  1449. // No legacy config exists for this key. 
  1450. if ( ! isset( $this->{$property} ) ) { 
  1451. continue; 
  1452.  
  1453. // Grab the value and record it as appropriate. 
  1454. $value = $this->{$property}; 
  1455.  
  1456. switch ( $property ) { 
  1457. case 'enable_create_step' : 
  1458. $lpc['screens']['create']['enabled'] = (bool) $value; 
  1459. break; 
  1460.  
  1461. case 'enable_edit_item' : 
  1462. $lpc['screens']['edit']['enabled'] = (bool) $value; 
  1463. break; 
  1464.  
  1465. case 'enable_admin_item' : 
  1466. $lpc['screens']['admin']['enabled'] = (bool) $value; 
  1467. break; 
  1468.  
  1469. case 'create_step_position' : 
  1470. $lpc['screens']['create']['position'] = $value; 
  1471. break; 
  1472.  
  1473. // Note: 'admin' becomes 'edit' to distinguish from Dashboard 'admin'. 
  1474. case 'admin_name' : 
  1475. $lpc['screens']['edit']['name'] = $value; 
  1476. break; 
  1477.  
  1478. case 'admin_slug' : 
  1479. $lpc['screens']['edit']['slug'] = $value; 
  1480. break; 
  1481.  
  1482. case 'create_name' : 
  1483. $lpc['screens']['create']['name'] = $value; 
  1484. break; 
  1485.  
  1486. case 'create_slug' : 
  1487. $lpc['screens']['create']['slug'] = $value; 
  1488. break; 
  1489.  
  1490. case 'admin_metabox_context' : 
  1491. $lpc['screens']['admin']['metabox_context'] = $value; 
  1492. break; 
  1493.  
  1494. case 'admin_metabox_priority' : 
  1495. $lpc['screens']['admin']['metabox_priority'] = $value; 
  1496. break; 
  1497.  
  1498. default : 
  1499. $lpc[ $property ] = $value; 
  1500. break; 
  1501.  
  1502. /** 
  1503. * Set up legacy properties. 
  1504. * 
  1505. * This method is responsible for ensuring that all legacy config 
  1506. * properties are stored in an array $this->legacy_properties, so that 
  1507. * they remain available to plugins that reference the variables at 
  1508. * their old locations. 
  1509. * 
  1510. * @since 1.8.0 
  1511. * 
  1512. * @see BP_Group_Extension::__get() 
  1513. */ 
  1514. protected function setup_legacy_properties() { 
  1515.  
  1516. // Only run this one time. 
  1517. if ( ! empty( $this->legacy_properties ) ) { 
  1518. return; 
  1519.  
  1520. $properties = $this->get_legacy_property_list(); 
  1521. $params = $this->params; 
  1522. $lp =& $this->legacy_properties; 
  1523.  
  1524. foreach ( $properties as $property ) { 
  1525. switch ( $property ) { 
  1526. case 'enable_create_step' : 
  1527. $lp['enable_create_step'] = $params['screens']['create']['enabled']; 
  1528. break; 
  1529.  
  1530. case 'enable_edit_item' : 
  1531. $lp['enable_edit_item'] = $params['screens']['edit']['enabled']; 
  1532. break; 
  1533.  
  1534. case 'enable_admin_item' : 
  1535. $lp['enable_admin_item'] = $params['screens']['admin']['enabled']; 
  1536. break; 
  1537.  
  1538. case 'create_step_position' : 
  1539. $lp['create_step_position'] = $params['screens']['create']['position']; 
  1540. break; 
  1541.  
  1542. // Note: 'admin' becomes 'edit' to distinguish from Dashboard 'admin'. 
  1543. case 'admin_name' : 
  1544. $lp['admin_name'] = $params['screens']['edit']['name']; 
  1545. break; 
  1546.  
  1547. case 'admin_slug' : 
  1548. $lp['admin_slug'] = $params['screens']['edit']['slug']; 
  1549. break; 
  1550.  
  1551. case 'create_name' : 
  1552. $lp['create_name'] = $params['screens']['create']['name']; 
  1553. break; 
  1554.  
  1555. case 'create_slug' : 
  1556. $lp['create_slug'] = $params['screens']['create']['slug']; 
  1557. break; 
  1558.  
  1559. case 'admin_metabox_context' : 
  1560. $lp['admin_metabox_context'] = $params['screens']['admin']['metabox_context']; 
  1561. break; 
  1562.  
  1563. case 'admin_metabox_priority' : 
  1564. $lp['admin_metabox_priority'] = $params['screens']['admin']['metabox_priority']; 
  1565. break; 
  1566.  
  1567. default : 
  1568. // All other items get moved over. 
  1569. $lp[ $property ] = $params[ $property ]; 
  1570.  
  1571. // Also reapply to the object, for backpat. 
  1572. $this->{$property} = $params[ $property ]; 
  1573.  
  1574. break; 
  1575.  
  1576. /** 
  1577. * Register a new Group Extension. 
  1578. * 
  1579. * @since 1.1.0 
  1580. * 
  1581. * @param string $group_extension_class Name of the Extension class. 
  1582. * @return false|null Returns false on failure, otherwise null. 
  1583. */ 
  1584. function bp_register_group_extension( $group_extension_class = '' ) { 
  1585.  
  1586. if ( ! class_exists( $group_extension_class ) ) { 
  1587. return false; 
  1588.  
  1589. // Register the group extension on the bp_init action so we have access 
  1590. // to all plugins. 
  1591. add_action( 'bp_init', function() use ( $group_extension_class ) { 
  1592. $extension = new $group_extension_class; 
  1593. add_action( 'bp_actions', array( &$extension, '_register' ), 8 ); 
  1594. add_action( 'admin_init', array( &$extension, '_register' ) ); 
  1595. }, 11 ); 
.