PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/role-scoper/admin/filters-admin_rs.php

https://bitbucket.org/broderboy/nycendurance-wordpress
PHP | 800 lines | 529 code | 181 blank | 90 comment | 150 complexity | 6c4e1ae26d1d9d0f22b9e6f741a7ba76 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-3.0, Apache-2.0, GPL-2.0, LGPL-2.1
  1. <?php
  2. if( basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME']) )
  3. die();
  4. require_once( dirname(__FILE__).'/admin_lib_rs.php' );
  5. /**
  6. * ScoperAdminFilters PHP class for the WordPress plugin Role Scoper
  7. * filters-admin_rs.php
  8. *
  9. * @author Kevin Behrens
  10. * @copyright Copyright 2012
  11. *
  12. */
  13. class ScoperAdminFilters
  14. {
  15. var $role_levels; // NOTE: user/role levels are used only for optional limiting of user edit, not for content filtering
  16. var $user_levels; // this is only populated for performance as a buffer for currently queried / listed users
  17. var $last_post_status = array();
  18. function ScoperAdminFilters() {
  19. global $scoper;
  20. // --------- CUSTOMIZABLE HOOK WRAPPING ---------
  21. //
  22. // Make all source-specific operation hooks trigger equivalent abstracted hook:
  23. // create_object_rs, edit_object_rs, save_object_rs, delete_object_rs,
  24. // create_term_rs, edit_term_rs, delete_term_rs (including custom non-WP taxonomies)
  25. //
  26. // These will be used by Role Scoper for role maintenance, but are also available for external use.
  27. $rs_filters = array();
  28. $rs_actions = array();
  29. // Register our abstract handlers to save_post, edit_post, delete_post and corresponding hooks from other data sources.
  30. // see core_default_data_sources() and Scoped_Data_Sources::process() for default hook names
  31. foreach ( $scoper->data_sources->get_all() as $src_name => $src ) {
  32. if ( ! empty($src->admin_actions) )
  33. foreach ( $src->admin_actions as $rs_hook => $original_hook ) {
  34. $rs_actions[$original_hook] = (object) array( 'name' => "{$rs_hook}_rs", 'rs_args' => "'$src_name', '' " );
  35. $rs_actions[$original_hook]->orig_num_args = ( 'save_post' == $original_hook ) ? 2 : 1;
  36. }
  37. if ( ! empty($src->admin_filters) )
  38. foreach ( $src->admin_filters as $rs_hook => $original_hook )
  39. $rs_filters[$original_hook] = (object) array( 'name' => "{$rs_hook}_rs", 'rs_args' => "'$src_name', '' " );
  40. // also register hooks that are specific to one object type
  41. foreach ( $src->object_types as $object_type => $otype_def ) {
  42. if ( ! empty($otype_def->admin_actions) ) {
  43. foreach ( $otype_def->admin_actions as $rs_hook => $original_hook ) {
  44. $rs_actions[$original_hook] = (object) array( 'name' => "{$rs_hook}_rs", 'rs_args' => "'$src_name', array( 'object_type' => '$object_type' ) " );
  45. $rs_actions[$original_hook]->orig_num_args = ( 'save_post' == $original_hook ) ? 2 : 1;
  46. }
  47. }
  48. if ( ! empty($otype_def->admin_filters) )
  49. foreach ( $otype_def->admin_filters as $rs_hook => $original_hook )
  50. $rs_filters[$original_hook] = (object) array( 'name' => "{$rs_hook}_rs", 'rs_args' => "'$src_name', array( 'object_type' => '$object_type' ) " );
  51. }
  52. } //foreach data_sources
  53. // Register our abstract handlers to create_category, edit_category, delete_category and corresponding hooks from other taxonomies.
  54. // (supports WP taxonomies AND custom taxonomies)
  55. // see core_default_taxonomies() and Scoped_Taxonomies::process() for default hook names
  56. foreach ( $scoper->taxonomies->get_all() as $taxonomy => $tx ) {
  57. if ( ! empty($tx->admin_actions) )
  58. foreach ( $tx->admin_actions as $rs_hook => $original_hook ) {
  59. if ( ! isset( $rs_actions[$original_hook] ) ) { // default term hooks changed from edit_{$taxonomy} to edit_term, etc. WP passes taxonomy. Keeping rs-passed taxonomy as 1st arg for back compat.
  60. $rs_actions[$original_hook] = (object) array( 'name' => "{$rs_hook}_rs", 'rs_args' => "'$taxonomy', '' " );
  61. $rs_actions[$original_hook]->orig_num_args = ( 'term_edit_ui' == $rs_hook ) ? 1 : 3;
  62. }
  63. }
  64. if ( ! empty($tx->admin_filters) )
  65. foreach ( $tx->admin_filters as $rs_hook => $original_hook )
  66. $rs_filters[$original_hook] = (object) array( 'name' => "{$rs_hook}_rs", 'rs_args' => "'$taxonomy', '' " );
  67. }
  68. // call our abstract handlers with a lambda function that passes in original hook name
  69. $hook_order = ( defined('WPCACHEHOME') ) ? -1 : 50; // WP Super Cache's early create_post / edit_post handlers clash with Role Scoper
  70. foreach ( $rs_actions as $original_hook => $rs_hook ) {
  71. if ( ! $original_hook ) continue;
  72. $arg_str = agp_get_lambda_argstring( $rs_hook->orig_num_args );
  73. $comma = ( $rs_hook->rs_args ) ? ',' : '';
  74. $func = "do_action( '$rs_hook->name', $rs_hook->rs_args $comma $arg_str );";
  75. //echo "adding action: $original_hook -> $func <br />";
  76. add_action( $original_hook, create_function( $arg_str, $func ), $hook_order, $rs_hook->orig_num_args );
  77. }
  78. foreach ( $rs_filters as $original_hook => $rs_hook ) {
  79. if ( ! $original_hook ) continue;
  80. $orig_hook_numargs = 1;
  81. $arg_str = agp_get_lambda_argstring($orig_hook_numargs);
  82. $comma = ( $rs_hook->rs_args ) ? ',' : '';
  83. $func = "return apply_filters( '$rs_hook->name', $arg_str $comma $rs_hook->rs_args );";
  84. //echo "adding filter: $original_hook -> $func <br />";
  85. add_filter( $original_hook, create_function( $arg_str, $func ), 50, $orig_hook_numargs );
  86. }
  87. // WP 2.5 throws a notice if plugins add their own hooks without prepping the global array
  88. // Source or taxonomy-specific hooks are mapped to these based on config member properties
  89. // in CR_Data_Sources::process() and CR_Taxonomies:process()
  90. $setargs = array( 'is_global' => true );
  91. $setkeys = array (
  92. 'create_object_rs', 'edit_object_rs', 'save_object_rs', 'delete_object_rs',
  93. 'create_term_rs', 'edit_term_rs', 'save_term_rs', 'delete_term_rs'
  94. );
  95. add_action('create_object_rs', array(&$this, 'mnt_create_object'), 10, 4);
  96. add_action('edit_object_rs', array(&$this, 'mnt_edit_object'), 10, 4);
  97. add_action('save_object_rs', array(&$this, 'mnt_save_object'), 10, 4);
  98. add_action('delete_object_rs', array(&$this, 'mnt_delete_object'), 10, 3);
  99. // these will be used even if the taxonomy in question is not a WP core taxonomy (i.e. even if uses a custom schema)
  100. add_action('create_term_rs', array(&$this, 'mnt_create_term'), 10, 4);
  101. add_action('edit_term_rs', array(&$this, 'mnt_edit_term'), 10, 4);
  102. add_action('delete_term_rs', array(&$this, 'mnt_delete_term'), 10, 3);
  103. // -------- Predefined WP User/Post/Page admin actions / filters ----------
  104. // user maintenace
  105. add_action('profile_update', array('ScoperAdminLib', 'sync_wproles') );
  106. add_action('set_user_role', array('ScoperAdminLib', 'schedule_role_sync') );
  107. add_filter('user_has_cap', array(&$this, 'flt_has_edit_user_cap'), 99, 3 );
  108. add_filter('editable_roles', array(&$this, 'flt_editable_roles'), 99 );
  109. if ( IS_MU_RS ) {
  110. add_action('remove_user_from_blog', array('ScoperAdminLib', 'delete_users'), 10, 2 );
  111. add_action('add_user_to_blog', array(&$this, 'act_schedule_user_sync'), 10, 3 ); // WP 3.0 multisite fires add_user_to_blog too early for us
  112. } else {
  113. //add_action('user_register', array('ScoperAdminLib', 'add_user') );
  114. add_action('user_register', array(&$this, 'act_schedule_user_sync'), 10, 3 );
  115. add_action('delete_user', array('ScoperAdminLib', 'delete_users') );
  116. }
  117. if ( GROUP_ROLES_RS )
  118. add_action('profile_update', array(&$this, 'act_update_user_groups'));
  119. // log post status transition to recognize new posts and status change to/from private
  120. add_action( 'transition_post_status', array(&$this, 'act_log_post_status'), 10, 3 );
  121. add_action( 'edit_post', array(&$this, 'act_log_updated_post') );
  122. // Filtering of Page Parent selection:
  123. add_filter('pre_post_status', array(&$this, 'flt_post_status'), 50, 1);
  124. add_filter('pre_post_parent', array(&$this, 'flt_page_parent'), 50, 1);
  125. add_filter( 'pre_post_tax_input', array(&$this, 'flt_tax_input'), 50, 1);
  126. if ( ( 'nav-menus.php' == $GLOBALS['pagenow'] ) ) {
  127. if ( scoper_get_option( 'admin_nav_menu_filter_items' ) ) {
  128. add_action( 'admin_head', array( &$this, 'act_nav_menu_header' ) );
  129. if ( ! empty( $_POST ) )
  130. add_action( 'pre_post_update', array(&$this, 'mnt_pre_post_update') );
  131. }
  132. add_action( 'admin_head', array( &$this, 'act_nav_menu_ui' ) );
  133. }
  134. add_action( 'check_admin_referer', array( &$this, 'act_nav_menu_guard_theme_locs' ) );
  135. // Filtering of terms selection:
  136. add_action('check_admin_referer', array(&$this, 'act_detect_post_presave')); // abuse referer check to work around a missing hook
  137. add_filter('pre_object_terms_rs', array(&$this, 'flt_pre_object_terms'), 50, 3);
  138. add_filter( 'save_post', array(&$this, 'custom_taxonomies_helper'), 5, 2);
  139. // TODO: also hook to "pre_option_default_{$taxonomy}"
  140. if ( ( 'options-writing.php' != $GLOBALS['pagenow'] ) && ! is_content_administrator_rs() )
  141. add_filter('pre_option_default_category', array(&$this, 'flt_default_term') );
  142. // Follow up on role creation / deletion by Role Manager, Capability Manager or other equivalent plugin
  143. // Role Manager / Capability Manager don't actually modify the stored role def until after the option update we're hooking on, so defer our maintenance operation
  144. global $wpdb;
  145. add_action( "update_option_{$wpdb->prefix}user_roles", array('ScoperAdminLib', 'schedule_role_sync') );
  146. add_filter( 'posts_fields', array(&$this, 'flt_posts_fields') );
  147. if ( scoper_get_option( 'group_ajax' ) ) {
  148. add_action( 'add_group_user_rs', array(&$this, 'new_group_user_notification'), 10, 3 );
  149. add_action( 'update_group_user_rs', array(&$this, 'edit_group_user_notification'), 10, 4 );
  150. }
  151. // TODO: make this optional
  152. // include private posts in the post count for each term
  153. global $wp_taxonomies;
  154. foreach ( $wp_taxonomies as $key => $t ) {
  155. if ( isset($t->update_count_callback) && ( '_update_post_term_count' == $t->update_count_callback ) )
  156. $wp_taxonomies[$key]->update_count_callback = 'scoper_update_post_term_count';
  157. }
  158. add_action( 'load-post.php', array( &$this, 'maybe_override_kses' ) );
  159. }
  160. function maybe_override_kses() {
  161. if ( ! empty($_POST) && ! empty($_POST['action']) && ( 'editpost' == $_POST['action'] ) ) {
  162. if ( current_user_can( 'unfiltered_html' ) ) // initial core cap check in kses_init() is unfilterable
  163. kses_remove_filters();
  164. }
  165. }
  166. // make sure pre filter is applied for all custom taxonomies regardless of term selection
  167. function custom_taxonomies_helper( $post_id, $post ) {
  168. require_once( dirname(__FILE__).'/filters-admin-save_rs.php' );
  169. scoper_force_custom_taxonomy_filters( $post_id, $post );
  170. }
  171. function act_log_post_status( $new_status, $old_status, $post ) {
  172. $this->last_post_status[$post->ID] = $old_status;
  173. }
  174. function act_log_updated_post( $post_id ) {
  175. $this->logged_post_update[$post_id] = true;
  176. }
  177. function new_group_user_notification ( $user_id, $group_id, $status ) {
  178. if ( 'active' == $status )
  179. return;
  180. require_once( dirname(__FILE__).'/group-notification_rs.php' );
  181. if ( 'requested' == $status )
  182. return ScoperGroupNotification::membership_request_notify( $user_id, $group_id );
  183. elseif ( 'recommended' == $status )
  184. return ScoperGroupNotification::membership_recommendation_notify( $user_id, $group_id );
  185. }
  186. function edit_group_user_notification ( $user_id, $group_id, $status, $prev_status ) {
  187. if ( $status == $prev_status )
  188. return;
  189. require_once( dirname(__FILE__).'/group-notification_rs.php' );
  190. if ( ! $prev_status )
  191. return $this->new_group_user_notification( $user_id, $group_id, $status );
  192. elseif ( 'active' == $status )
  193. return ScoperGroupNotification::membership_activation_notify( $user_id, $group_id, true );
  194. elseif ( 'recommended' == $status )
  195. return ScoperGroupNotification::membership_recommendation_notify( $user_id, $group_id, true );
  196. }
  197. // optional filter for WP role edit based on user level
  198. function flt_editable_roles( $roles ) {
  199. if ( defined( 'DISABLE_QUERYFILTERS_RS' ) || ! scoper_get_option('limit_user_edit_by_level') )
  200. return $roles;
  201. require_once( dirname(__FILE__).'/user_lib_rs.php' );
  202. return ScoperUserEdit::editable_roles( $roles );
  203. }
  204. // Optionally, prevent anyone from editing or deleting a user whose level is higher than their own
  205. function flt_has_edit_user_cap($wp_blogcaps, $orig_reqd_caps, $args) {
  206. if ( ! defined( 'DISABLE_QUERYFILTERS_RS' ) && ( in_array( 'edit_users', $orig_reqd_caps ) || in_array( 'delete_users', $orig_reqd_caps ) ) && ! empty($args[2]) ) {
  207. if ( scoper_get_option('limit_user_edit_by_level') ) {
  208. require_once( dirname(__FILE__).'/user_lib_rs.php' );
  209. $wp_blogcaps = ScoperUserEdit::has_edit_user_cap( $wp_blogcaps, $orig_reqd_caps, $args );
  210. }
  211. }
  212. return $wp_blogcaps;
  213. }
  214. function flt_posts_fields($cols) {
  215. if ( defined( 'DISABLE_QUERYFILTERS_RS' ) )
  216. return $cols;
  217. // possible TODO: reinstate support for separate activation of pages / posts lean (as of WP 2.9, all post types us edit.php)
  218. if ( ( defined( 'SCOPER_EDIT_PAGES_LEAN' ) || defined( 'SCOPER_EDIT_POSTS_LEAN' ) ) && ( 'edit.php' == $GLOBALS['pagenow'] ) ) {
  219. global $wpdb;
  220. $cols = "$wpdb->posts.ID, $wpdb->posts.post_author, $wpdb->posts.post_date, $wpdb->posts.post_date_gmt, $wpdb->posts.post_title, $wpdb->posts.post_status, $wpdb->posts.comment_status, $wpdb->posts.ping_status, $wpdb->posts.post_password, $wpdb->posts.post_name, $wpdb->posts.to_ping, $wpdb->posts.pinged, $wpdb->posts.post_parent, $wpdb->posts.post_modified, $wpdb->posts.post_modified_gmt, $wpdb->posts.guid, $wpdb->posts.post_type, $wpdb->posts.post_mime_type, $wpdb->posts.menu_order, $wpdb->posts.comment_count";
  221. }
  222. return $cols;
  223. }
  224. // Filtering of Page Parent selection.
  225. // This is a required after-the-fact operation for WP < 2.7 (due to inability to control inclusion of Main Page in UI dropdown)
  226. // For WP >= 2.7, it is an anti-hacking precaution
  227. //
  228. // There is currently no way to explictly restrict or grant Page Association rights to Main Page (root). Instead:
  229. // * Require blog-wide edit_others_pages cap for association of a page with Main
  230. // * If an unqualified user tries to associate or un-associate a page with Main Page,
  231. // revert page to previously stored parent if possible. Otherwise set status to "unpublished".
  232. function flt_post_status ($status) {
  233. if ( defined( 'DISABLE_QUERYFILTERS_RS' ) )
  234. return $status;
  235. require_once( dirname(__FILE__).'/filters-admin-save_rs.php');
  236. return scoper_flt_post_status($status);
  237. }
  238. // Enforce any page parent filtering which may have been dictated by the flt_post_status filter, which executes earlier.
  239. function flt_page_parent ($parent_id) {
  240. if ( defined( 'DISABLE_QUERYFILTERS_RS' ) )
  241. return $parent_id;
  242. require_once( dirname(__FILE__).'/filters-admin-save_rs.php');
  243. return scoper_flt_page_parent($parent_id);
  244. }
  245. function act_detect_post_presave($action) {
  246. // for post update with no post categories checked, insert a fake category so WP core doesn't force default category
  247. // (flt_pre_object_terms will first restore any existing postcats dropped due to user's lack of permissions)
  248. if ( 0 === strpos($action, 'update-post_') ) {
  249. if ( defined( 'DISABLE_QUERYFILTERS_RS' ) )
  250. return;
  251. if ( empty($_POST['post_category']) && ! is_content_administrator_rs() ) {
  252. $_POST['post_category'] = array(-1);
  253. }
  254. }
  255. }
  256. function flt_tax_input( $tax_input ) {
  257. if ( $tax_input && is_array($tax_input) ) {
  258. foreach( $tax_input as $taxonomy => $terms ) {
  259. if ( is_string($terms) ) // currently, don't restrict non-hierarchical tag assignments / removals per-user
  260. continue;
  261. //$terms = explode( ",", $terms );
  262. $tax_input[$taxonomy] = $this->flt_pre_object_terms( $terms, $taxonomy );
  263. }
  264. }
  265. return $tax_input;
  266. }
  267. function flt_pre_object_terms ($selected_terms, $taxonomy, $args = array()) {
  268. if ( defined( 'DISABLE_QUERYFILTERS_RS' ) || did_action('tdomf_create_post_start') ) // don't filter out a category that was added by TDO Mini Forms
  269. return $selected_terms;
  270. require_once( dirname(__FILE__).'/filters-admin-save_rs.php');
  271. return scoper_flt_pre_object_terms($selected_terms, $taxonomy, $args);
  272. }
  273. // This handler is meant to fire whenever an object is inserted or updated.
  274. // If the client does use such a hook, we will force it by calling internally from mnt_create and mnt_edit
  275. function mnt_save_object($src_name, $args, $object_id, $object = '') {
  276. //rs_errlog( 'mnt_save_object' );
  277. if ( defined( 'RVY_VERSION' ) ) {
  278. global $revisionary;
  279. if ( ! empty($revisionary->admin->revision_save_in_progress) ) {
  280. $revisionary->admin->revision_save_in_progress = false;
  281. return;
  282. }
  283. }
  284. require_once( dirname(__FILE__).'/filters-admin-save_rs.php');
  285. scoper_mnt_save_object($src_name, $args, $object_id, $object);
  286. if ( function_exists('relevanssi_query') ) {
  287. require_once( dirname(__FILE__).'/relevanssi-helper-admin_rs.php' );
  288. Relevanssi_Admin_Helper_RS::rvi_reindex();
  289. }
  290. }
  291. function _can_edit_theme_locs() {
  292. return is_content_administrator_rs() || ! empty( $GLOBALS['current_user']->allcaps['manage_nav_menus'] ) || ! empty( $GLOBALS['current_user']->allcaps['switch_themes'] );
  293. }
  294. // users with editing access to a limited subset of menus are not allowed to set menu for theme locations
  295. function act_nav_menu_ui() {
  296. if ( ! $this->_can_edit_theme_locs() ) {
  297. unset( $GLOBALS['wp_meta_boxes']['nav-menus']['side']['default']['nav-menu-theme-locations'] );
  298. }
  299. }
  300. // make sure theme locations are not wiped because logged user has editing access to a subset of menus
  301. function act_nav_menu_guard_theme_locs( $referer ) {
  302. if ( 'update-nav_menu' == $referer ) {
  303. if ( isset( $_POST['menu-locations'] ) ) {
  304. if ( ! $this->_can_edit_theme_locs() )
  305. unset( $_POST['menu-locations'] );
  306. }
  307. }
  308. }
  309. function act_nav_menu_header() {
  310. if ( ! is_content_administrator_rs() ) {
  311. require_once( dirname(__FILE__).'/filters-admin-nav_menus_rs.php' );
  312. _rs_disable_uneditable_items_ui();
  313. }
  314. }
  315. function mnt_pre_post_update( $object_id ) {
  316. if ( ! is_content_administrator_rs() ) {
  317. // don't allow modification of menu items for posts which user can't edit (not currently feasible since WP fires update for each menu item even if unmodified)
  318. require_once( dirname(__FILE__).'/filters-admin-nav_menus_rs.php' );
  319. _rs_mnt_modify_nav_menu_item( $object_id, 'edit' );
  320. }
  321. }
  322. // This handler is meant to fire only on updates, not new inserts
  323. function mnt_edit_object($src_name, $args, $object_id, $object = '') {
  324. static $edited_objects;
  325. if ( ! isset($edited_objects) )
  326. $edited_objects = array();
  327. // so this filter doesn't get called by hook AND internally
  328. if ( isset($edited_objects[$src_name][$object_id]) )
  329. return;
  330. $edited_objects[$src_name][$object_id] = 1;
  331. // call save handler directly in case it's not registered to a hook
  332. $this->mnt_save_object($src_name, $args, $object_id, $object);
  333. }
  334. function mnt_delete_object($src_name, $args, $object_id) {
  335. $object = '';
  336. $defaults = array( 'object_type' => '', 'object' => '' );
  337. $args = array_intersect_key( $defaults, (array) $args );
  338. extract($args);
  339. if ( ! $object_id )
  340. return;
  341. // don't allow deletion of menu items for posts which user can't edit
  342. if ( ( 'nav-menus.php' == $GLOBALS['pagenow'] ) && ! is_content_administrator_rs() && ! empty( $_POST ) && scoper_get_option( 'admin_nav_menu_filter_items' ) ) {
  343. require_once( dirname(__FILE__).'/filters-admin-nav_menus_rs.php' );
  344. _rs_mnt_modify_nav_menu_item( $object_id, 'delete' );
  345. }
  346. // could defer role/cache maint to speed potential bulk deletion, but script may be interrupted before admin_footer
  347. $this->item_deletion_aftermath( OBJECT_SCOPE_RS, $src_name, $object_id );
  348. if ( empty($object_type) )
  349. $object_type = cr_find_object_type($src_name, $object_id);
  350. if ( 'post' == $src_name ) {
  351. if ( $post_type_obj = get_post_type_object( $object_type ) ) {
  352. if ( $post_type_obj->hierarchical )
  353. scoper_flush_cache_groups('get_pages');
  354. }
  355. }
  356. scoper_flush_roles_cache(OBJECT_SCOPE_RS);
  357. }
  358. function mnt_create_object($src_name, $args, $object_id, $object = '') {
  359. $defaults = array( 'object_type' => '' );
  360. $args = array_intersect_key( $defaults, (array) $args );
  361. extract($args);
  362. static $inserted_objects;
  363. if ( ! isset($inserted_objects) )
  364. $inserted_objects = array();
  365. // so this filter doesn't get called by hook AND internally
  366. if ( isset($inserted_objects[$src_name][$object_id]) )
  367. return;
  368. if ( empty($object_type) )
  369. if ( $col_type = $GLOBALS['scoper']->data_sources->member_property($src_name, 'cols', 'type') )
  370. $object_type = ( isset($object->$col_type) ) ? $object->$col_type : '';
  371. if ( empty($object_type) ) {
  372. if ( ! isset( $object ) )
  373. $object = '';
  374. $object_type = cr_find_object_type($src_name, $object_id, $object);
  375. }
  376. if ( $object_type == 'revision' )
  377. return;
  378. $inserted_objects[$src_name][$object_id] = 1;
  379. if ( 'post' == $src_name ) {
  380. $post_type_obj = get_post_type_object( $object_type );
  381. if ( $post_type_obj->hierarchical )
  382. scoper_flush_cache_groups('get_pages');
  383. }
  384. }
  385. function mnt_create_term($deprecated_taxonomy, $args, $term_id, $unused_tt_id = '', $taxonomy = '') {
  386. if ( ! $taxonomy )
  387. $taxonomy = $deprecated_taxonomy;
  388. $this->mnt_save_term( $taxonomy, $args, $term_id, $unused_tt_id, $taxonomy );
  389. scoper_term_cache_flush();
  390. delete_option( "{$taxonomy}_children_rs" );
  391. }
  392. function mnt_edit_term($deprecated_taxonomy, $args, $term_ids, $unused_tt_id = '', $taxonomy = '') {
  393. if ( ! $taxonomy )
  394. $taxonomy = $deprecated_taxonomy;
  395. static $edited_terms;
  396. if ( ! isset($edited_terms) )
  397. $edited_terms = array();
  398. // bookmark edit passes an array of term_ids
  399. $term_ids = (array) $term_ids;
  400. foreach ( $term_ids as $term_id ) {
  401. // so this filter doesn't get called by hook AND internally
  402. if ( isset($edited_terms[$taxonomy][$term_id]) )
  403. return;
  404. $edited_terms[$taxonomy][$term_id] = 1;
  405. // call save handler directly in case it's not registered to a hook
  406. $this->mnt_save_term( $taxonomy, $args, $term_id, $unused_tt_id, $taxonomy );
  407. }
  408. }
  409. // This handler is meant to fire whenever a term is inserted or updated.
  410. // If the client does use such a hook, we will force it by calling internally from mnt_create and mnt_edit
  411. function mnt_save_term($deprecated_taxonomy, $args, $term_id, $unused_tt_id = '', $taxonomy = '') {
  412. require_once( dirname(__FILE__).'/filters-admin-save_rs.php');
  413. scoper_mnt_save_term( $deprecated_taxonomy, $args, $term_id, $unused_tt_id, $taxonomy );
  414. }
  415. function mnt_delete_term($deprecated_taxonomy, $args, $term_id, $unused_tt_id = '', $taxonomy = '') {
  416. global $wpdb;
  417. if ( ! $term_id )
  418. return;
  419. if ( ! $taxonomy )
  420. $taxonomy = $deprecated_taxonomy;
  421. // could defer role/cache maint to speed potential bulk deletion, but script may be interrupted before admin_footer
  422. $this->item_deletion_aftermath( TERM_SCOPE_RS, $taxonomy, $term_id );
  423. delete_option( "{$taxonomy}_children_rs" );
  424. scoper_term_cache_flush();
  425. scoper_flush_roles_cache(TERM_SCOPE_RS, '', '', $taxonomy);
  426. scoper_flush_cache_flag_once("rs_$taxonomy");
  427. }
  428. function item_deletion_aftermath( $scope, $src_or_tx_name, $obj_or_term_id ) {
  429. global $wpdb;
  430. // delete role assignments for deleted term
  431. if ( $ass_ids = scoper_get_col("SELECT assignment_id FROM $wpdb->user2role2object_rs WHERE src_or_tx_name = '$src_or_tx_name' AND scope = '$scope' AND obj_or_term_id = '$obj_or_term_id'") ) {
  432. $id_in = "'" . implode("', '", $ass_ids) . "'";
  433. scoper_query("DELETE FROM $wpdb->user2role2object_rs WHERE assignment_id IN ($id_in)");
  434. // Propagated roles will be converted to direct-assigned roles if the original progenetor goes away. Removal of a "link" in the parent/child propagation chain has no effect.
  435. scoper_query("UPDATE $wpdb->user2role2object_rs SET inherited_from = '0' WHERE inherited_from IN ($id_in)");
  436. }
  437. if ( $req_ids = scoper_get_col("SELECT requirement_id FROM $wpdb->role_scope_rs WHERE topic = '$scope' AND src_or_tx_name = '$src_or_tx_name' AND obj_or_term_id = '$obj_or_term_id'") ) {
  438. $id_in = "'" . implode("', '", $req_ids) . "'";
  439. scoper_query("DELETE FROM $wpdb->role_scope_rs WHERE requirement_id IN ($id_in)");
  440. // Propagated requirements will be converted to direct-assigned roles if the original progenetor goes away. Removal of a "link" in the parent/child propagation chain has no effect.
  441. scoper_query("UPDATE $wpdb->role_scope_rs SET inherited_from = '0' WHERE inherited_from IN ($id_in)");
  442. }
  443. }
  444. function act_update_user_groups($user_id) {
  445. if ( empty( $_POST['rs_editing_user_groups'] ) ) // otherwise we'd delete group assignments if another plugin calls do_action('profile_update') unexpectedly
  446. return;
  447. global $current_rs_user;
  448. $editable_group_ids = array();
  449. $stored_groups = array();
  450. if ( $user_id == $current_rs_user->ID )
  451. $stored_groups['active'] = $current_rs_user->groups;
  452. else {
  453. $user = rs_get_user($user_id, '', array( 'skip_role_merge' => 1 ) );
  454. $stored_groups['active'] = $user->groups;
  455. }
  456. // by retrieving filtered groups here, user will only modify membership for groups they can administer
  457. $editable_group_ids['active'] = ScoperAdminLib::get_all_groups(FILTERED_RS, COL_ID_RS, array( 'reqd_caps' => 'manage_groups' ) );
  458. if( scoper_get_option( 'group_ajax' ) ) {
  459. $this->update_user_groups_multi_status( $user_id, $stored_groups, $editable_group_ids );
  460. return;
  461. } else {
  462. $stored_groups = $stored_groups['active'];
  463. $editable_group_ids = $editable_group_ids['active'];
  464. }
  465. if ( ! empty($_POST['groups_csv']) ) {
  466. if ( $csv_for_item = ScoperAdminLib::agent_ids_from_csv( 'groups_csv', 'groups' ) )
  467. $posted_groups = array_merge($posted_groups, $csv_for_item);
  468. } else
  469. $posted_groups = ( isset($_POST['group']) ) ? $_POST['group'] : array();
  470. $posted_groups = array_unique( $posted_groups );
  471. foreach ($editable_group_ids as $group_id) {
  472. if( in_array($group_id, $posted_groups) ) { // checkbox is checked
  473. if( ! isset($stored_groups[$group_id]) )
  474. ScoperAdminLib::add_group_user($group_id, $user_id);
  475. } elseif( isset($stored_groups[$group_id]) ) {
  476. ScoperAdminLib::remove_group_user($group_id, $user_id);
  477. }
  478. }
  479. }
  480. function update_user_groups_multi_status( $user_id, $stored_groups, $editable_group_ids ) {
  481. global $current_rs_user;
  482. $posted_groups = array();
  483. $is_administrator = is_user_administrator_rs();
  484. $can_manage = $is_administrator || current_user_can( 'manage_groups' );
  485. $can_moderate = $can_manage || current_user_can( 'recommend_group_membership' );
  486. if ( ! $can_moderate && ! current_user_can( 'request_group_membership' ) )
  487. return;
  488. if ( $can_manage )
  489. $posted_groups['active'] = explode( ',', trim($_POST['current_agents_rs_csv'], '') );
  490. else
  491. $stored_groups = array_diff_key( $stored_groups, array( 'active' => true ) );
  492. if ( $can_moderate ) {
  493. $posted_groups['recommended'] = ! empty($_POST['recommended_agents_rs_csv']) ? explode( ',', trim($_POST['recommended_agents_rs_csv'], '') ) : array();
  494. $stored_groups['recommended'] = array_fill_keys( $current_rs_user->get_groups_for_user( $current_rs_user->ID, array( 'status' => 'recommended' ) ), true );
  495. $editable_group_ids['recommended'] = ScoperAdminLib::get_all_groups(FILTERED_RS, COL_ID_RS, array( 'reqd_caps' => 'recommend_group_membership' ) );
  496. if ( isset($editable_group_ids['active']) )
  497. $editable_group_ids['recommended'] = array_unique( $editable_group_ids['recommended'] + $editable_group_ids['active'] );
  498. }
  499. $stored_groups['requested'] = array_fill_keys( $current_rs_user->get_groups_for_user( $current_rs_user->ID, array( 'status' => 'requested' ) ), true );
  500. $editable_group_ids['requested'] = ScoperAdminLib::get_all_groups(FILTERED_RS, COL_ID_RS, array( 'reqd_caps' => 'request_group_membership' ) );
  501. if ( isset($editable_group_ids['recommended']) )
  502. $editable_group_ids['requested'] = array_unique( $editable_group_ids['requested'] + $editable_group_ids['recommended'] );
  503. $posted_groups['requested'] = ! empty($_POST['requested_agents_rs_csv']) ? explode( ',', trim($_POST['requested_agents_rs_csv'], '') ) : array();
  504. $all_posted_groups = agp_array_flatten( $posted_groups );
  505. $all_stored_groups = array();
  506. foreach ( array_keys($stored_groups) as $status )
  507. $all_stored_groups = $all_stored_groups + $stored_groups[$status];
  508. foreach ( $stored_groups as $status => $stored ) {
  509. if ( ! $editable_group_ids[$status] )
  510. continue;
  511. // remove group memberships which were not posted for any status, if logged user can edit the group
  512. foreach ( array_keys($stored) as $group_id ) {
  513. if ( ! in_array( $group_id, $all_posted_groups ) )
  514. if ( in_array( $group_id, $editable_group_ids[$status] ) )
  515. ScoperAdminLib::remove_group_user($group_id, $user_id);
  516. }
  517. }
  518. foreach ( $posted_groups as $status => $posted ) {
  519. if ( ! $editable_group_ids[$status] )
  520. continue;
  521. // insert or update group memberships as specified, if logged user can edit the group
  522. foreach ( $posted as $group_id ) {
  523. if ( in_array( $group_id, $editable_group_ids[$status] ) ) {
  524. if ( ! in_array( $group_id, $all_stored_groups ) )
  525. ScoperAdminLib::add_group_user($group_id, $user_id, $status);
  526. elseif ( ! in_array( $group_id, $stored_groups[$status] ) )
  527. ScoperAdminLib::update_group_user($group_id, $user_id, $status);
  528. }
  529. }
  530. }
  531. }
  532. function act_schedule_user_sync( $user_id, $role_name = '', $blog_id = '' ) {
  533. // ScoperAdminLib::add_user applies default group(s), calls sync_wproles
  534. $func = create_function( '', "ScoperAdminLib::add_user('" . $user_id . "');" );
  535. add_action( 'shutdown', $func );
  536. }
  537. function flt_default_term( $default_term_id, $taxonomy = 'category' ) {
  538. require_once( dirname(__FILE__).'/filters-admin-term-selection_rs.php');
  539. // support an array of default IDs (but don't require it)
  540. $term_ids = (array) $default_term_id;
  541. $user_terms = array(); // will be returned by filter_terms_for_status
  542. $term_ids = scoper_filter_terms_for_status($taxonomy, $term_ids, $user_terms);
  543. // if the default term is not in user's subset of usable terms, substitute first available
  544. if ( ( ( ! $term_ids ) || ! $term_ids[0] ) && $user_terms ) {
  545. if ( $GLOBALS['scoper']->taxonomies->member_property( $taxonomy, 'requires_term' ) )
  546. return $user_terms[0];
  547. else
  548. return;
  549. }
  550. if ( count($term_ids) > 1 ) // won't return an array unless an array was passed in and more than one of its elements is usable by this user
  551. return $term_ids;
  552. elseif( $term_ids )
  553. return reset( $term_ids ); // if a single term ID was passed in and is permitted, it is returned here
  554. }
  555. function user_can_associate_main( $post_type ) {
  556. if ( is_content_administrator_rs() )
  557. return true;
  558. if ( ! $post_type_obj = get_post_type_object($post_type) )
  559. return true;
  560. if ( ! $post_type_obj->hierarchical )
  561. return true;
  562. // currently used only for page type, or for all if constant is set
  563. $top_pages_locked = scoper_get_option( 'lock_top_pages' );
  564. if ( ( 'page' == $post_type ) || defined( 'SCOPER_LOCK_OPTION_ALL_TYPES' ) ) {
  565. if ( '1' === $top_pages_locked ) {
  566. // only administrators can change top level structure
  567. return false;
  568. } else {
  569. $reqd_caps = ( 'author' === $top_pages_locked ) ? array( $post_type_obj->cap->publish_posts ) : array( $post_type_obj->cap->edit_others_posts );
  570. $roles = $GLOBALS['scoper']->role_defs->qualify_roles($reqd_caps);
  571. return array_intersect_key($roles, $GLOBALS['current_rs_user']->blog_roles[ANY_CONTENT_DATE_RS]);
  572. }
  573. }
  574. }
  575. } // end class
  576. function init_role_assigner() {
  577. global $scoper_role_assigner;
  578. if ( ! isset($scoper_role_assigner) ) {
  579. require_once( dirname(__FILE__).'/role_assigner_rs.php');
  580. $scoper_role_assigner = new ScoperRoleAssigner();
  581. }
  582. return $scoper_role_assigner;
  583. }
  584. function scoper_flush_cache_flag_once ($cache_flag) {
  585. static $flushed_wpcache_flags;
  586. if ( ! isset($flushed_wpcache_flags) )
  587. $flushed_wpcache_flags = array();
  588. if ( ! isset( $flushed_wpcache_flags[$cache_flag]) ) {
  589. wpp_cache_flush_group($cache_flag);
  590. $flushed_wpcache_flags[$cache_flag] = true;
  591. }
  592. }
  593. // flush a specified portion of Role Scoper's persistant cache
  594. function scoper_flush_cache_groups($base_cache_flag) {
  595. $scoper_role_types = array('rs', 'wp', 'wp_cap');
  596. foreach ( $scoper_role_types as $role_type ) {
  597. scoper_flush_cache_flag_once($role_type . '_' . $base_cache_flag . '_for_groups' );
  598. scoper_flush_cache_flag_once($role_type . '_' . $base_cache_flag . '_for_user' );
  599. scoper_flush_cache_flag_once($role_type . '_' . $base_cache_flag . '_for_ug' );
  600. }
  601. }
  602. function scoper_term_cache_flush() {
  603. // flush_cache_groups will expand this base flag to "rs_get_terms_for_user", etc.
  604. scoper_flush_cache_groups('get_terms');
  605. scoper_flush_cache_groups('scoper_get_terms');
  606. scoper_flush_cache_flag_once('all_terms');
  607. // TODO: implement this for custom taxonomies that are hierarchical and use taxonomies?
  608. //if ( scoper_get_otype_option( 'use_term_roles', 'post', 'page' ) )
  609. // scoper_flush_cache_groups('get_pages');
  610. }
  611. // modifies WP core _update_post_term_count to include private posts in the count, since RS roles can grant access to them
  612. function scoper_update_post_term_count( $terms ) {
  613. global $wpdb;
  614. foreach ( (array) $terms as $term ) {
  615. $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->posts.ID = $wpdb->term_relationships.object_id AND post_status IN ('publish', 'private') AND term_taxonomy_id = %d", $term ) );
  616. $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
  617. }
  618. }
  619. ?>