PageRenderTime 51ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/role-scoper/query-interceptor_rs.php

https://bitbucket.org/broderboy/nycendurance-wordpress
PHP | 1517 lines | 953 code | 319 blank | 245 comment | 393 complexity | 9b10864ca15e79de19b7bea6cdeb2075 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-3.0, Apache-2.0, GPL-2.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. if( basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME']) )
  3. die();
  4. /**
  5. * QueryInterceptor_RS PHP class for the WordPress plugin Role Scoper
  6. * query-interceptor_rs.php
  7. *
  8. * @author Kevin Behrens
  9. * @copyright Copyright 2011
  10. *
  11. */
  12. class QueryInterceptor_RS
  13. {
  14. var $scoper;
  15. var $skip_teaser; // this is only used by templates making a direct call to query_posts but wishing to get non-teased results
  16. var $last_request = array();
  17. function QueryInterceptor_RS() {
  18. $this->scoper =& $GLOBALS['scoper'];
  19. $is_administrator = is_content_administrator_rs();
  20. // ---- ABSTRACT ROLE SCOPER HOOKS - wrap around source-specific hooks based on DataSources config ------
  21. //
  22. // Request / Where Filter:
  23. // Support filtering of any query request (WP or plugin-defined) based on scoped roles.
  24. // Resulting content may be narrowed or expanded from WP core results.
  25. // (currently require request/where/join string as first hook arg, ignore other args)
  26. //
  27. // Results Teaser:
  28. // Alternately, if a results hook is defined, unqualified records can
  29. // be left in the result set, but with the content stripped and the excerpt
  30. // replaced or appended with a teaser message.
  31. // (currently require results as array of objects in first hook arg, ignore other args)
  32. //
  33. // suported filter interface:
  34. // request hooks: $arg1 = full request query
  35. // results hooks: $arg1 = results set
  36. // filter args: $item, $src_name, $object_type, $args (note: to customize other args, filter must be called directly)
  37. add_filter('objects_where_rs', array(&$this, 'flt_objects_where'), 2, 4);
  38. add_filter('objects_request_rs', array(&$this, 'flt_objects_request'), 2, 4);
  39. add_filter('objects_results_rs', array(&$this, 'flt_objects_results'), 50, 4);
  40. add_filter('objects_teaser_rs', array(&$this, 'flt_objects_teaser'), 50, 4);
  41. if ( ! $this->scoper->direct_file_access ) {
  42. // Append any limiting clauses to WHERE clause for taxonomy query
  43. // args: ($where, $taxonomy, $object_type = '', $reqd_op = '') e.g. ($where, 'categories', 'post', 'edit')
  44. // Note: If any of the optional args are missing or nullstring, an attempt is made
  45. // to determine them from URI based on Scoped_Taxonomy properties
  46. add_filter('terms_request_rs', array(&$this, 'flt_terms_request'), 50, 4);
  47. add_filter('terms_where_rs', array(&$this, 'flt_terms_where'), 50, 3);
  48. }
  49. // note: If DISABLE_QUERYFILTERS_RS is set, the RS filters are still defined above for selective internal use,
  50. // but in that case are not mapped to the defined data source hooks ('posts_where', etc.) below
  51. if ( ! defined('DISABLE_QUERYFILTERS_RS') ) {
  52. //in effect, make WP pass the hook name so multiple hooks can be registered to a single handler
  53. $rs_hooks = array();
  54. foreach ( $this->scoper->data_sources->get_all() as $src_name => $src ) {
  55. if ( empty($src->query_hooks) )
  56. continue;
  57. if ( ! $is_administrator ) {
  58. if ( isset($src->query_hooks->where) ) {
  59. $rs_hooks[$src->query_hooks->where] = (object) array( 'name' => 'objects_where_rs', 'rs_args' => "'$src_name', '', '' ");
  60. } elseif ( isset($src->query_hooks->request) )
  61. $rs_hooks[$src->query_hooks->request] = (object) array( 'name' => 'objects_request_rs', 'rs_args' => "'$src_name', '', '' ");
  62. }
  63. // log results (to identify restricted posts) even for admin. Also, possibly apply front end teaser
  64. if ( isset($src->query_hooks->results) )
  65. $rs_hooks[$src->query_hooks->results] = (object) array( 'name' => 'objects_results_rs', 'rs_args' => "'$src_name', '', '' ");
  66. } //foreach data_sources
  67. if ( ! $is_administrator ) {
  68. // use late-firing filter so teaser filtering is also applied to sticky posts
  69. add_filter( 'the_posts', array( &$this, 'flt_the_posts' ), 50, 2 );
  70. // manually hook posts_request to pass on object_type value in the referenced wp_query object
  71. add_filter( 'posts_request', array( &$this, 'flt_posts_request'), 50, 2 );
  72. }
  73. // ...but don't include this hook in the filter-wrapping loop below
  74. if ( isset( $rs_hooks['posts_request'] ) )
  75. unset( $rs_hooks['posts_request'] );
  76. // call our abstract handlers with a lambda function that passes in original hook name
  77. foreach ( $rs_hooks as $original_hook => $rs_hook ) {
  78. if ( ! $original_hook )
  79. continue;
  80. $arg_str = '$a';
  81. $comma = ( $rs_hook->rs_args ) ? ',' : '';
  82. $func = "return apply_filters( '$rs_hook->name', $arg_str $comma $rs_hook->rs_args );";
  83. add_filter( $original_hook, create_function( $arg_str, $func ), 50, 1 );
  84. //d_echo ("adding filter: $original_hook -> $func <br />");
  85. }
  86. }
  87. //add_filter( 'posts_request', array( &$this, 'flt_debug_query'), 999 );
  88. }
  89. //function flt_debug_query( $query ) {
  90. // d_echo( $query . '<br /><br />' );
  91. // return $query;
  92. //}
  93. function flt_posts_request( $request, $_wp_query = false ) {
  94. if ( is_object( $_wp_query ) && ! empty( $_wp_query->query_vars['post_type'] ) ) {
  95. $object_types = $_wp_query->query_vars['post_type'];
  96. if ( 'any' == $object_types )
  97. $object_types = '';
  98. } else
  99. $object_types = '';
  100. return $this->flt_objects_request( $request, 'post', $object_types );
  101. }
  102. // Append any limiting clauses to WHERE clause for taxonomy query
  103. //$reqd_caps_by_taxonomy[tx_name][op_type] = array of cap names
  104. function flt_terms_request($request, $taxonomies, $args = array()) {
  105. //$defaults = array( 'reqd_caps_by_otype' => array(), 'is_term_admin' => false, 'required_operation' => '', 'post_type' => '' );
  106. // determine term id col (term_id or term_taxonomy_id) for term management queries
  107. if ( strpos( $request, 'AS tt' ) )
  108. $args['term_id_col'] = 'tt.term_taxonomy_id';
  109. elseif ( strpos( $request, 'AS t' ) )
  110. $args['term_id_col'] = 't.term_id';
  111. else {
  112. global $wpdb;
  113. if ( strpos( $request, $wpdb->terms ) )
  114. $args['term_id_col'] = "$wpdb->terms.term_id";
  115. elseif ( strpos( $request, $wpdb->term_taxonomy ) )
  116. $args['term_id_col'] = "$wpdb->term_taxonomy.term_taxonomy_id";
  117. }
  118. if ( $rs_where = $this->flt_terms_where('', $taxonomies, $args) ) {
  119. if ( strpos( $request, ' WHERE ' ) )
  120. $request = str_replace( ' WHERE ', " WHERE 1=1 $rs_where AND ", $request );
  121. elseif ( $pos_suffix = agp_get_suffix_pos( $request ) )
  122. $request = substr( $request, 0, $pos_suffix ) . " WHERE 1=1 $rs_where " . substr( $request, $pos_suffix );
  123. else
  124. $request .= " WHERE 1=1 $rs_where ";
  125. }
  126. //d_echo ("<br /><br />terms_request output:$request<br /><br />");
  127. return $request;
  128. }
  129. function flt_terms_where($where, $taxonomies, $args = array()) {
  130. $defaults = array( 'reqd_caps_by_otype' => array(), 'is_term_admin' => false, 'required_operation' => '', 'post_type' => '', 'term_id_col' => '' );
  131. $args = array_merge( $defaults, (array) $args );
  132. extract( $args, EXTR_SKIP );
  133. $taxonomies = (array) $taxonomies;
  134. if ( ! $taxonomies )
  135. return $where;
  136. if ( $post_type )
  137. $post_type = (array) $post_type;
  138. // support multiple taxonomies, but only if they all use the same object data source
  139. $taxonomy_sources = array();
  140. foreach ( $taxonomies as $taxonomy ) {
  141. if ( ! $this->scoper->taxonomies->is_member( $taxonomy ) )
  142. continue;
  143. $src_name = $this->scoper->taxonomies->member_property( $taxonomy, 'object_source' );
  144. if ( is_object($src_name) ) // support legacy code which stored object variable to property (TODO: eliminate this)
  145. $src_name = $src_name->name;
  146. $taxonomy_sources[$src_name] = true;
  147. }
  148. if ( count($taxonomy_sources) != 1 )
  149. return $where;
  150. // if the filter call did not specify required caps...
  151. if ( ! $reqd_caps_by_otype ) {
  152. $reqd_caps_by_otype = array();
  153. // try to determine context from URI (if taxonomy definition includes such clues)
  154. foreach( $taxonomies as $taxonomy ) {
  155. $reqd_caps_by_otype = array_merge( $reqd_caps_by_otype, $this->scoper->get_terms_reqd_caps( $taxonomy, $required_operation, $is_term_admin ) ); // NOTE: get_terms_reqd_caps() returns term management caps on edit-tags.php, otherwise post edit caps
  156. }
  157. if ( $post_type )
  158. $reqd_caps_by_otype = array_intersect_key( $reqd_caps_by_otype, array_flip( $post_type ) );
  159. // if required operation still unknown, default based on access type
  160. if ( ! $reqd_caps_by_otype )
  161. return $where;
  162. }
  163. // prevent hardway-admin filtering of any queries which may be triggered by this filter
  164. $GLOBALS['scoper_status']->querying_db = true;
  165. // Note that term management capabilities (i.e. "manage_categories") are implemented via Term Roles on the Posts data source, with taxonomy as the object type
  166. //
  167. // if this is a term management query, no need to involve objects query filtering
  168. if ( ( 'post' == $src_name ) && isset( $reqd_caps_by_otype[ $taxonomies[0] ] ) ) {
  169. $qualifying_roles = $this->scoper->role_defs->qualify_roles( $reqd_caps_by_otype[$taxonomies[0]], 'rs', $taxonomies[0] ); // otherwise qualify_terms() will not filter out other taxonomy manager roles that also use manage_categories cap
  170. if ( ! $term_id_col ) {
  171. // can't attempt filtering without this info
  172. $GLOBALS['scoper_status']->querying_db = false;
  173. return $where;
  174. }
  175. $return_id_type = ( strpos( $term_id_col, 'term_taxonomy_id' ) ) ? COL_TAXONOMY_ID_RS : COL_ID_RS;
  176. if ( $ids = $this->scoper->qualify_terms( $reqd_caps_by_otype[$taxonomies[0]], $taxonomies[0], $qualifying_roles, compact('return_id_type') ) ) { // returns term_id since COL_TAXONOMY_ID_RS is not passed as args['return_id_type']
  177. $where .= " AND $term_id_col IN ('" . implode( "','", $ids ) . "')";
  178. } else
  179. $where = ' AND 1=2';
  180. } else {
  181. // Call objects_where_role_clauses() with src_name of the taxonomy source.
  182. // This works as a slight subversion of the normal flt_objects_where query building because we are also forcing taxonomies explicitly and passing the terms_query arg.
  183. $args['terms_query'] = true;
  184. $args['use_object_roles'] = false;
  185. $args['skip_owner_clause'] = true;
  186. $args['terms_reqd_caps'] = $reqd_caps_by_otype;
  187. $args['taxonomies'] = $taxonomies;
  188. if ( is_admin() )
  189. $args['alternate_reqd_caps'][0] = array( "assign_$taxonomy" );
  190. $where .= $this->flt_objects_where('', $src_name, '', $args);
  191. // For Edit Form display, include currently stored terms. User will still not be able to remove them without proper editing roles for object. (TODO: abstract for other data sources)
  192. if ( ( 'post.php' == $GLOBALS['pagenow'] ) && empty( $_REQUEST['admin_bar'] ) ) {
  193. if ( 'post' == $src_name ) {
  194. if ( $object_id = $this->scoper->data_sources->detect( 'id', $src_name ) ) {
  195. if ( $stored_terms = wp_get_object_terms( $object_id, $taxonomies[0] ) ) {
  196. $tt_ids = array();
  197. foreach( array_keys($stored_terms) as $key )
  198. $tt_ids []= $stored_terms[$key]->term_taxonomy_id;
  199. $where .= " OR tt.term_taxonomy_id IN ('" . implode( "','", $tt_ids ) . "')";
  200. }
  201. }
  202. }
  203. }
  204. }
  205. $GLOBALS['scoper_status']->querying_db = false; // re-enable hardway-admin filtering
  206. return $where;
  207. }
  208. function flt_objects_request($request, $src_name, $object_types = '', $args = array()) {
  209. if ( $args ) {
  210. $defaults = array( 'skip_teaser' => false );
  211. $args = array_diff_key($args, array_flip( array('request', 'src_name', 'object_types' ) ) );
  212. $args = array_merge( $defaults, (array) $args );
  213. extract($args);
  214. }
  215. // if Media Library filtering is disabled, don't filter listing for TinyMCE popup either
  216. if ( is_admin() && defined( 'SCOPER_ALL_UPLOADS_EDITABLE' ) && strpos( $_SERVER['SCRIPT_NAME'], 'wp-admin/media-upload.php' ) )
  217. return $request;
  218. // Filtering in user_has_cap sufficiently controls revision access; a match here should be for internal, pre-validation purposes
  219. if ( strpos( $request, "post_type = 'revision'") )
  220. return $request;
  221. // no need to apply objects query filtering within NextGEN Gallery / Grand Flash Gallery upload operation (was failing with undefined $current_user)
  222. if ( is_admin() ) {
  223. if ( defined( 'SCOPER_ALL_UPLOADS_EDITABLE' ) && ( $GLOBALS['pagenow'] == 'upload.php' ) )
  224. return $request;
  225. $nofilter_scripts = array( '/admin/upload.php' );
  226. if ( $nofilter_scripts = apply_filters( 'noqueryfilter_scripts_rs', $nofilter_scripts ) ) {
  227. foreach( $nofilter_scripts as $_script_name ) {
  228. if ( false !== strpos( $_SERVER['SCRIPT_NAME'], $_script_name ) )
  229. return $request;
  230. }
  231. }
  232. }
  233. // prevent hardway-admin filtering of any queries which may be triggered by this filter
  234. $GLOBALS['scoper_status']->querying_db = true;
  235. if ( empty($skip_teaser) ) {
  236. $this->last_request[$src_name] = $request; // Store for potential use by subsequent teaser filter
  237. }
  238. //$request = agp_force_distinct($request); // in case data source didn't provide a hook for objects_distinct
  239. if ( ! preg_match('/\s*WHERE\s*1=1/', $request) )
  240. $request = preg_replace('/\s*WHERE\s*/', ' WHERE 1=1 AND ', $request);
  241. $pos_where = 0;
  242. $pos_suffix = 0;
  243. $where = agp_parse_after_WHERE_11( $request, $pos_where, $pos_suffix ); // any existing where, orderby or group by clauses remain in $where
  244. if ( ! $pos_where && $pos_suffix ) {
  245. $request = substr($request, 0, $pos_suffix) . ' WHERE 1=1' . substr($request, $pos_suffix);
  246. $pos_where = $pos_suffix;
  247. }
  248. if ( 'post' == $src_name ) {
  249. // If the query uses an alias for the posts table, be sure to use that alias in the WHERE clause also.
  250. //
  251. // NOTE: if query refers to non-active blog, this code will prevent a DB syntax error, but will not cause the correct roles / restrictions to be applied.
  252. // Other plugins need to use switch_to_blog() rather than just executing a query on a non-main blog.
  253. $matches = array();
  254. if ( $return = preg_match( '/SELECT .* FROM [^ ]+posts AS ([^ ]) .*/', $request, $matches ) )
  255. $args['source_alias'] = $matches[2];
  256. elseif ( $return = preg_match( '/SELECT .* FROM ([^ ]+)posts .*/', $request, $matches ) )
  257. $args['source_alias'] = $matches[1] . 'posts';
  258. }
  259. // TODO: abstract this
  260. if ( strpos( $request, "post_type = 'attachment'" ) ) {
  261. global $wpdb;
  262. if ( ! is_admin() && ! empty($_REQUEST['attachment_id']) && ! defined('SCOPER_BLOCK_UNATTACHED_UPLOADS') ) {
  263. if ( $_att = get_post($_REQUEST['attachment_id']) ) {
  264. if ( 0 === $_att->post_parent )
  265. return $request;
  266. }
  267. }
  268. // filter attachments by inserting a scoped subquery based on user roles on the post/page attachment is tied to
  269. $rs_where = $this->flt_objects_where( '', $src_name, '', $args );
  270. $subqry = "SELECT ID FROM $wpdb->posts WHERE 1=1 $rs_where";
  271. if ( is_admin() ) {
  272. // The listed objects are attachments, so query filter is based on objects they inherit from
  273. $admin_others_attached = scoper_get_option( 'admin_others_attached_files' );
  274. $admin_others_unattached = scoper_get_option( 'admin_others_unattached_files' );
  275. if ( ( ! $admin_others_attached ) || ! $admin_others_unattached )
  276. $can_edit_others_blogwide = $this->scoper->user_can_edit_blogwide( 'post', '', array( 'require_others_cap' => true, 'status' => 'publish' ) );
  277. global $current_user;
  278. // optionally hide other users' unattached uploads, but not from blog-wide Editors
  279. if ( $admin_others_unattached || $can_edit_others_blogwide )
  280. $author_clause = '';
  281. else
  282. $author_clause = "AND $wpdb->posts.post_author = '{$current_user->ID}'";
  283. if ( is_admin() && ( ! defined('SCOPER_BLOCK_UNATTACHED_UPLOADS') || ! SCOPER_BLOCK_UNATTACHED_UPLOADS ) )
  284. $unattached_clause = "( $wpdb->posts.post_parent = 0 $author_clause ) OR";
  285. else
  286. $unattached_clause = '';
  287. $attached_clause = ( $admin_others_attached || $can_edit_others_blogwide ) ? '' : "AND $wpdb->posts.post_author = '{$current_user->ID}'";
  288. $request = str_replace( "$wpdb->posts.post_type = 'attachment'", "( $wpdb->posts.post_type = 'attachment' AND ( $unattached_clause ( $wpdb->posts.post_parent IN ($subqry) $attached_clause ) ) )", $request );
  289. } else
  290. $request = str_replace( "$wpdb->posts.post_type = 'attachment'", "( $wpdb->posts.post_type = 'attachment' AND ( $wpdb->posts.post_parent IN ($subqry) ) )", $request );
  291. } else {
  292. // Generate a query filter based on roles for the listed objects
  293. $rs_where = $this->flt_objects_where($where, $src_name, $object_types, $args);
  294. if ( $pos_where === false )
  295. $request = $request . ' WHERE 1=1 ' . $where;
  296. else
  297. $request = substr($request, 0, $pos_where) . ' WHERE 1=1 ' . $rs_where; // any pre-exising join clauses remain in $request
  298. }
  299. // re-enable hardway-admin filtering
  300. $GLOBALS['scoper_status']->querying_db = false;
  301. //d_echo( "<br />filtered: $request<br /><br />" );
  302. return $request;
  303. }
  304. // called by flt_objects_where, flt_objects_results
  305. function _get_object_types( $src, $object_types = '' ) {
  306. if ( ! $object_types ) {
  307. if ( ! is_object($src) )
  308. if ( ! $src = $this->scoper->data_sources->get($src) )
  309. return array();
  310. return array_keys($src->object_types); // include all defined otypes in the query if none were specified
  311. } else
  312. return (array) $object_types; // make sure the passed-in value is an array
  313. }
  314. // called by flt_objects_where, flt_objects_results
  315. function _get_teaser_object_types($src_name, $object_types, $args = array()) {
  316. $args = (array) $args;
  317. if ( ! empty($args['skip_teaser']) || is_admin() || is_content_administrator_rs() || is_attachment_rs() || defined('XMLRPC_REQUEST') || ! empty($this->skip_teaser) )
  318. return array();
  319. if ( is_feed() && defined( 'SCOPER_NO_FEED_TEASER' ) )
  320. return array();
  321. if ( ( ! empty( $args['required_operation'] ) && ( 'read' != $args['required_operation'] ) ) )
  322. return array();
  323. if ( empty($object_types) )
  324. $object_types = $this->_get_object_types($src_name);
  325. $tease_otypes = array();
  326. if ( scoper_get_otype_option('do_teaser', $src_name) ) {
  327. global $current_user;
  328. foreach ( $object_types as $object_type )
  329. if ( scoper_get_otype_option('use_teaser', $src_name, $object_type) ) {
  330. $teased_users = scoper_get_otype_option( 'teaser_logged_only', $src_name, $object_type );
  331. if ( empty( $teased_users )
  332. || ( ( 'anon' == $teased_users ) && empty($current_user->ID) )
  333. || ( ( 'anon' != $teased_users ) && ! empty($current_user->ID) ) )
  334. $tease_otypes []= $object_type;
  335. }
  336. }
  337. return $tease_otypes;
  338. }
  339. // NOTE: Setting use_object_roles or use_term_roles to a boolean forces enable/disable for all types / taxonomies. Otherwise stored options will be retrieved for each object type / taxonomy.
  340. function flt_objects_where($where, $src_name, $object_types = '', $args = array() ) {
  341. $defaults = array( 'user' => '', 'use_object_roles' => -1, 'use_term_roles' => -1,
  342. 'taxonomies' => array(), 'request' => '', 'terms_query' => 0,
  343. 'force_reqd_caps' => '', 'alternate_reqd_caps' => '', 'source_alias' => '',
  344. 'required_operation' => '', 'terms_reqd_caps' => '', 'skip_teaser' => false
  345. );
  346. $args = array_merge( $defaults, (array) $args );
  347. extract($args);
  348. // filtering in user_has_cap sufficiently controls revision access; a match here should be for internal, pre-validation purposes
  349. if ( strpos( $where, "post_type = 'revision'") )
  350. return $where;
  351. $where_prepend = '';
  352. //rs_errlog ("object_where input: $where");
  353. //rs_errlog ('');
  354. //d_echo ("<br /><strong>object_where input:</strong> $where<br />");
  355. //echo "<br />$where<br />";
  356. if ( ! is_object($user) ) {
  357. $user = $GLOBALS['current_rs_user'];
  358. $args['user'] = $user;
  359. }
  360. if ( ! $src = $this->scoper->data_sources->get($src_name) )
  361. return $where; // the specified data source is not know to Role Scoper
  362. $src_table = ( ! empty($source_alias) ) ? $source_alias : $src->table;
  363. // verify table name and id col definition (the actual existance checked at time of admin entry)
  364. if ( ! ($src->table && $src->cols->id) ) {
  365. rs_notice( sprintf( 'Role Scoper Configuration Error: table_basename or col_id are undefined for the %s data source.', $src_name) );
  366. return $where;
  367. }
  368. // need to allow ambiguous object type for special cap requirements like comment filtering
  369. $object_types = $this->_get_object_types($src, $object_types);
  370. $tease_otypes = array_intersect( $object_types, $this->_get_teaser_object_types($src_name, $object_types, $args) );
  371. if ( ! empty($src->no_object_roles) )
  372. $use_object_roles = false;
  373. if ( $terms_query && $terms_reqd_caps ) {
  374. foreach ( array_keys($terms_reqd_caps) as $_object_type )
  375. $otype_status_reqd_caps[$_object_type][''] = $terms_reqd_caps[$_object_type]; // terms request does not support multiple statuses
  376. } else {
  377. if ( $force_reqd_caps && is_array($force_reqd_caps) ) {
  378. $otype_status_reqd_caps = $force_reqd_caps;
  379. } else {
  380. global $wpdb;
  381. if ( ! $required_operation )
  382. $required_operation = ( 'front' == CURRENT_ACCESS_NAME_RS ) ? OP_READ_RS : OP_EDIT_RS;
  383. $preview_future = strpos( $where, "$wpdb->posts.post_name =" ) || strpos( $where, "$wpdb->posts.ID =" );
  384. if ( ! $otype_status_reqd_caps = cr_get_reqd_caps( $src_name, $required_operation, -1, -1, false, $preview_future ) )
  385. return $where;
  386. }
  387. $otype_status_reqd_caps = array_intersect_key($otype_status_reqd_caps, array_flip($object_types) );
  388. }
  389. // Since Role Scoper can restrict or expand access regardless of post_status, query must be modified such that
  390. // * the default owner inclusion clause "OR post_author = [user_id] AND post_status = 'private'" is removed
  391. // * all statuses are listed apart from owner inclusion clause (and each of these status clauses is subsequently replaced with a scoped equivalent which imposes any necessary access limits)
  392. // * a new scoped owner clause is constructed where appropriate (see $where[$cap_name]['owner'] in function objects_where_role_clauses()
  393. //
  394. if ( $src->cols->owner && $user->ID ) {
  395. // force standard query padding
  396. $where = preg_replace("/{$src->cols->owner}\s*=\s*/", "{$src->cols->owner} = ", $where);
  397. $where = str_replace( " {$src->cols->owner} =", " $src_table.{$src->cols->owner} =", $where);
  398. $where = str_replace( " {$src->cols->owner} IN", " $src_table.{$src->cols->owner} IN", $where);
  399. }
  400. if ( ! empty($src->query_replacements) ) {
  401. foreach ( $src->query_replacements as $find => $replace ) {
  402. // for posts_request, remove the owner inclusion clause "OR post_author = [user_id] AND post_status = 'private'" because we'll account for each status based on properties of required caps
  403. $find_ = str_replace('[user_id]', $user->ID, $find);
  404. if ( false !== strpos($find_, '[') || false !== strpos($find_, ']') ) {
  405. rs_notice( sprintf( 'Role Scoper Config Error: invalid query clause search criteria for %1$s (%2$s).<br /><br />Valid placeholders are:<br />', $src_name, $find) . print_r(array_keys($map)) );
  406. return ' AND 1=2 ';
  407. }
  408. $replace_ = str_replace('[user_id]', $user->ID, $replace);
  409. if ( false !== strpos($replace_, '[') || false !== strpos($replace_, ']') ) {
  410. rs_notice( sprintf( 'Role Scoper Config Error: invalid query clause replacement criteria for %1$s (%2$s).<br /><br />Valid placeholders are:<br />', $src_name, $replace) . print_r(array_keys($map)) );
  411. return ' AND 1=2 ';
  412. }
  413. $where = str_replace($find_, $replace_, $where);
  414. }
  415. }
  416. $force_single_type = false;
  417. $col_type = ( ! empty( $src->cols->type ) ) ? $src->cols->type : '';
  418. if ( $col_type ) {
  419. // If the passed request contains a single object type criteria, maintain that status exclusively (otherwise include type-specific conditions for each available type)
  420. $matches = array();
  421. $num_matches = preg_match_all( "/$col_type\s*=\s*'([^']+)'/", $where, $matches );
  422. if ( 1 == $num_matches ) {
  423. $force_single_type = true;
  424. $object_types = array( $matches[1][0] );
  425. if ( $matched_reqd_caps = array_intersect_key( $otype_status_reqd_caps, array_flip($object_types) ) ) // sanity check prevents running with an empty reqd_caps array if something goes wrong with otype detection
  426. $otype_status_reqd_caps = $matched_reqd_caps;
  427. }
  428. }
  429. if ( ( 'post' == $src_name ) && ! array_intersect( $object_types, array_keys( array_intersect( scoper_get_option( 'use_post_types' ), array( true ) ) ) ) )
  430. return $where;
  431. elseif ( empty( $otype_status_reqd_caps) )
  432. return ' AND 1=2 ';
  433. $basic_status_clause = array();
  434. $force_single_status = false;
  435. $status_clause_pos = 0;
  436. $col_status = ( ! empty( $src->cols->status ) ) ? $src->cols->status : '';
  437. if ( $col_status ) {
  438. // force standard query padding
  439. $where = preg_replace("/$col_status\s*=\s*'/", "$col_status = '", $where);
  440. $where = str_replace(" $col_status =", " {$src_table}.$col_status =", $where);
  441. $where = str_replace(" $col_status IN", " {$src_table}.$col_status IN", $where);
  442. foreach ( array_keys( $otype_status_reqd_caps ) as $listing_otype )
  443. foreach( array_keys( $otype_status_reqd_caps[$listing_otype] ) as $status )
  444. $basic_status_clause[$status] = "{$src_table}.$col_status = '$status'";
  445. // If the passed request contains a single status criteria, maintain that status exclusively (otherwise include status-specific conditions for each available status)
  446. // (But not if user is anon and hidden content teaser is enabled. In that case, we need to replace the default "status=publish" clause)
  447. $matches = array();
  448. if ( $num_matches = preg_match_all( "/{$src_table}.$col_status\s*=\s*'([^']+)'/", $where, $matches ) )
  449. $status_clause_pos = strpos( $where, $matches[0][0] ); // note the match position for use downstream
  450. if ( 1 == $num_matches ) {
  451. $use_status = $matches[1][0];
  452. // Eliminate a primary plugin incompatibility by skipping this preservation of existing single status requirements if we're on the front end and the requirement is 'publish'.
  453. // (i.e. include private posts that this user has access to via RS role assignment).
  454. if ( ! $this->scoper->is_front() || ( 'publish' != $use_status ) || ( empty( $args['user']->ID ) && empty($tease_otypes) ) || defined('SCOPER_RETAIN_PUBLISH_FILTER') ) {
  455. $force_single_status = true;
  456. foreach ( array_keys($otype_status_reqd_caps) as $_object_type )
  457. $otype_status_reqd_caps[$_object_type] = array_intersect_key( $otype_status_reqd_caps[$_object_type], array( $use_status => true ) );
  458. }
  459. }
  460. } else {
  461. // this source doesn't define statuses
  462. $basic_status_clause = array ( '' => '');
  463. }
  464. if ( empty($skip_teaser) && ! array_diff($object_types, $tease_otypes) ) {
  465. if ( $status_clause_pos && $force_single_type ) { // All object types potentially returned by this query will have a teaser filter applied to results, so we don't need to filter the query
  466. // override our sanity safeguard against exposing private posts to anonymous readers
  467. if ( empty($user->ID) ) {
  468. // Since we're dropping out of this function early in advance of teaser filtering,
  469. // must take this opportunity to add private status to the query (otherwise WP excludes private for anon user)
  470. // (But don't do this if teaser is configured to hide private content)
  471. $check_otype = ( count($tease_otypes) && in_array('post', $tease_otypes) ) ? 'post' : $tease_otypes[0];
  472. $post_type_obj = get_post_type_object($check_otype);
  473. if ( ! scoper_get_otype_option('teaser_hide_private', $src_name, $check_otype) && ( ! $post_type_obj->hierarchical || scoper_get_otype_option('private_items_listable', 'post', 'page') ) ) {
  474. if ( $col_status && isset( $otype_status_reqd_caps[$check_otype] ) ) {
  475. $status_or = "{$src_table}.$col_status = '" . implode("' OR {$src_table}.$col_status = '", array_keys($otype_status_reqd_caps[$check_otype]) ) . "'";
  476. $where = str_replace( $basic_status_clause['publish'], "( $status_or )", $where);
  477. } else {
  478. $where = str_replace( $basic_status_clause['publish'], "1=1", $where);
  479. }
  480. }
  481. }
  482. }
  483. return $where;
  484. }
  485. $is_administrator = is_content_administrator_rs(); // make sure administrators never have content limited
  486. $status_or = '';
  487. $status_where = array();
  488. foreach ($otype_status_reqd_caps as $object_type => $status_reqd_caps) {
  489. if ( ! is_array($status_reqd_caps) ) {
  490. rs_notice( sprintf( 'Role Scoper Configuration Error: reqd_caps for the %s data source must be array[operation][object_type][status] where operation is "read", "edit" or "admin".', $src_name) );
  491. return $where;
  492. }
  493. // don't bother generating these parameters if we're just going to pass the object type through for teaser filtering
  494. if ( ! in_array($object_type, $tease_otypes) ) {
  495. if ( true === $use_term_roles ) { // if boolean true was passed in, force usage of all term roles
  496. if ( 'post' == $src_name ) {
  497. //$otype_use_term_roles = array_fill_keys( get_taxonomies( array( 'public' => true, 'object_type' => $object_type ) ), 1 );
  498. $otype_use_term_roles = array();
  499. foreach( get_taxonomies( array( 'public' => true ), 'object' ) as $taxonomy => $taxonomy_obj )
  500. if ( in_array( $object_type, $taxonomy_obj->object_type ) )
  501. $otype_use_term_roles[$taxonomy] = 1;
  502. } else
  503. $otype_use_term_roles = ( ! empty( $src->uses_taxonomies ) ) ? array_fill_keys( $src->uses_taxonomies, true ) : array();
  504. } else {
  505. $check_object_type = ( 'link_category' == $object_type ) ? 'link' : $object_type;
  506. $otype_use_term_roles = ( -1 == $use_term_roles ) ? scoper_get_otype_option('use_term_roles', $src_name, $check_object_type) : false;
  507. }
  508. if ( ( ! $otype_use_term_roles ) && $terms_query )
  509. continue;
  510. // if a boolean was passed in, override the stored option
  511. $otype_use_object_roles = ( -1 == $use_object_roles ) ? scoper_get_otype_option('use_object_roles', $src_name, $object_type) : $use_object_roles;
  512. } else {
  513. $otype_use_term_roles = false;
  514. $otype_use_object_roles = false;
  515. }
  516. //now step through all statuses and corresponding cap requirements for this otype and access type
  517. // (will replace "col_status = status_name" with "col_status = status_name AND ( [scoper requirements] )
  518. foreach ($status_reqd_caps as $status_name => $reqd_caps) {
  519. if ( 'trash' == $status_name ) // in wp-admin, we need to include trash posts for the count query, but not for the listing query unless trash status is requested
  520. if ( ( empty($this->last_request[$src_name]) || ! strpos($this->last_request[$src_name], 'COUNT') ) && ( empty( $_GET['post_status'] ) || ( 'trash' != $_GET['post_status'] ) ) )
  521. continue;
  522. if ( $is_administrator )
  523. $status_where[$status_name][$object_type] = '1=1';
  524. elseif ( empty($skip_teaser) && in_array($object_type, $tease_otypes) )
  525. if ( $terms_query && ! $otype_use_object_roles )
  526. $status_where[$status_name][$object_type] = '1=1';
  527. else
  528. $status_where[$status_name][$object_type] = "{$src_table}.{$src->cols->type} = '$object_type'"; // this object type will be teaser-filtered
  529. else {
  530. // filter defs for otypes which don't define a status will still have a single status element with value ''
  531. $args = array_merge( $args, array( 'object_type' => $object_type, 'otype_use_term_roles' => $otype_use_term_roles, 'otype_use_object_roles' => $otype_use_object_roles ) );
  532. $clause = $this->objects_where_role_clauses($src_name, $reqd_caps, $args);
  533. if ( empty($clause) || ( '1=2' == $clause ) ) // this means no qualifying roles are available
  534. $status_where[$status_name][$object_type] = '1=2';
  535. // array key order status/object is reversed intentionally for subsequent processing
  536. elseif ( (count($otype_status_reqd_caps) > 1) && ( ! $terms_query || $otype_use_object_roles) ) // more than 1 object type
  537. $status_where[$status_name][$object_type] = "( {$src_table}.{$src->cols->type} = '$object_type' AND ( $clause ) )";
  538. else
  539. $status_where[$status_name][$object_type] = $clause;
  540. }
  541. }
  542. }
  543. // all otype clauses concat: object_type1 clause [OR] [object_type2 clause] [OR] ...
  544. foreach ( array_keys($status_where) as $status_name ) {
  545. if ( isset($preserve_or_clause[$status_name]) )
  546. $status_where[$status_name][] = $preserve_or_clause[$status_name];
  547. if ( $tease_otypes )
  548. $check_otype = ( count($tease_otypes) && in_array('post', $tease_otypes) ) ? 'post' : $tease_otypes[0];
  549. // extra line of defense: even if upstream logic goes wrong, never disclose a private item to anon user (but if the where clause was passed in with explicit status=private, must include our condition)
  550. if ( ('private' == $status_name) && ! $force_single_status && empty($GLOBALS['current_user']->ID) && ( ! $tease_otypes || scoper_get_otype_option('teaser_hide_private', $src_name, $check_otype) ) )
  551. unset( $status_where[$status_name] );
  552. else
  553. $status_where[$status_name] = agp_implode(' ) OR ( ', $status_where[$status_name], ' ( ', ' ) ');
  554. }
  555. // combine identical status clauses
  556. $duplicate_clause = array();
  557. $replace_clause = array();
  558. if ( $col_status && count($status_where) > 1 ) { // more than one status clause
  559. foreach ( $status_where as $status_name => $status_clause) {
  560. if ( isset($duplicate_clause[$status_name]) )
  561. continue;
  562. reset($status_where);
  563. if ( $other_status_name = array_search($status_clause, $status_where) ) {
  564. if ( $other_status_name == $status_name ) $other_status_name = array_search($status_clause, $status_where);
  565. if ( $other_status_name && ( $other_status_name != $status_name ) ) {
  566. $duplicate_clause[$other_status_name][$status_name] = true;
  567. $replace_clause[$status_name] = true;
  568. }
  569. }
  570. }
  571. }
  572. $status_where = array_diff_key($status_where, $replace_clause);
  573. foreach ( $status_where as $status_name => $this_status_where) {
  574. if ( $status_clause_pos && $force_single_status ) {
  575. //We are maintaining the single status which was specified in original query
  576. if ( ! $this_status_where || ( $this_status_where == '1=2' ) )
  577. $where_prepend = '1=2';
  578. elseif ( $this_status_where == '1=1' )
  579. $where_prepend = '';
  580. else {
  581. //insert at original status clause position
  582. $where_prepend = '';
  583. $where = substr($where, 0, $status_clause_pos) . "( $this_status_where ) AND " . substr($where, $status_clause_pos);
  584. }
  585. break;
  586. }
  587. // We may be replacing or inserting status clauses
  588. if ( ! empty($duplicate_clause[$status_name]) ) {
  589. // We generated duplicate clauses for some statuses
  590. foreach ( array_keys($duplicate_clause[$status_name]) as $other_status_name ) {
  591. $where = str_replace($basic_status_clause[$other_status_name], '1=2', $where);
  592. }
  593. $duplicate_clause[$status_name] = array_merge($duplicate_clause[$status_name], array($status_name=>1) );
  594. if ( $col_status ) {
  595. $name_in = "'" . implode("', '", array_keys($duplicate_clause[$status_name])) . "'";
  596. $status_prefix = "{$src_table}.$col_status IN ($name_in)";
  597. } else {
  598. $status_prefix = "1=1";
  599. }
  600. } elseif ( $col_status && $status_name ) {
  601. $status_prefix = $basic_status_clause[$status_name];
  602. } else
  603. $status_prefix = '';
  604. if ( $this_status_where && ( $this_status_where != '1=2' || count($status_where) > 1 ) ) { //todo: confirm we can OR the 1=2 even if only one status clause
  605. if ( '1=1' == $this_status_where )
  606. $status_clause = ( $status_prefix ) ? "$status_prefix " : '';
  607. else {
  608. $status_clause = ( $col_status && $status_prefix ) ? "$status_prefix AND " : '';
  609. $status_clause .= "( $this_status_where )"; // TODO: reduce number of parentheses
  610. $status_clause = " ( $status_clause )";
  611. }
  612. } else
  613. $status_clause = '1=2';
  614. if ( $status_clause ) {
  615. if ( $col_status && $status_name && strpos($where, $basic_status_clause[$status_name]) ) {
  616. // Replace existing status clause with our scoped equivalent
  617. $where = str_replace($basic_status_clause[$status_name], "$status_clause", $where);
  618. } elseif ( $status_clause_pos && ( $status_clause != '1=2' ) ) {
  619. // This status was not in the original query, but we now insert it with scoping clause at the position of another existing status clause
  620. $where = substr($where, 0, $status_clause_pos) . "$status_clause OR " . substr($where, $status_clause_pos);
  621. } else {
  622. // Default query makes no mention of status (perhaps because this data source doesn't define statuses),
  623. // so prepend this clause to front of where clause
  624. $where_prepend .= "$status_or $status_clause";
  625. $status_or = ' OR';
  626. }
  627. }
  628. }
  629. // Existance of this variable means no status clause exists in default WHERE. AND away we go.
  630. // Prepend so we don't disturb any orderby/groupby/limit clauses which are along for the ride
  631. if ( $where_prepend ) {
  632. if ( $where )
  633. $where = " AND ( $where_prepend ) $where";
  634. else
  635. $where = " AND ( $where_prepend )";
  636. }
  637. //d_echo ("<br /><br /><strong>objects_where output:</strong> $where<br /><br />");
  638. //echo "<br />$where<br />";
  639. //rs_errlog ("object_where output: $where");
  640. //rs_errlog ('');
  641. //rs_errlog ('');
  642. return $where;
  643. }
  644. // core Role Scoper where clause concatenation called by listing filter (flt_objects_request) and single access filter (flt_user_has_cap)
  645. // $reqd_caps[cap_name] = min scope
  646. //
  647. // required args: user, object_type, otype_use_object_roles, otype_use_term_roles
  648. //
  649. function objects_where_role_clauses($src_name, $reqd_caps, $args = array() ) {
  650. $defaults = array( 'taxonomies' => array(), 'terms_query' => false, 'alternate_reqd_caps' => '',
  651. 'custom_user_blogcaps' => '', 'skip_owner_clause' => false, 'require_full_object_role' => false );
  652. // Required Args
  653. // NOTE: use_object_roles is a boolean for the single object_type in question, but otype_use_object_roles is array[taxonomy] = true or false
  654. $required = array_fill_keys( array( 'user', 'object_type', 'otype_use_term_roles', 'otype_use_object_roles' ), true );
  655. if ( $missing = array_diff_key( $required, $args ) ) {
  656. rs_notice ( sprintf( 'Role Scoper Runtime Error (%1$s) - Missing argument(s): %2$s', 'objects_where_scope_clauses', implode( ", ", array_keys($missing) ) ) );
  657. return ' 1=2 ';
  658. }
  659. $defaults = array_merge( $defaults, $required );
  660. $args = array_merge( $defaults, (array) $args );
  661. extract($args);
  662. if ( '' === $custom_user_blogcaps )
  663. $custom_user_blogcaps = SCOPER_CUSTOM_USER_BLOGCAPS;
  664. $reqd_caps = (array) $reqd_caps;
  665. $reqd_caps = $this->scoper->role_defs->role_handles_to_caps($reqd_caps);
  666. // accomodate editing of published posts/pages to revision
  667. if ( defined( 'RVY_VERSION' ) && rvy_get_option('pending_revisions') ) {
  668. if ( empty( $GLOBALS['revisionary']->skip_revision_allowance ) ) {
  669. $revision_uris = apply_filters( 'scoper_revision_uris', array( 'edit.php', 'upload.php', 'widgets.php', 'admin-ajax.php', 'rvy-revisions' ) );
  670. if ( is_admin() || ! empty( $_GET['preview'] ) )
  671. $revision_uris []= 'index.php';
  672. $plugin_page = is_admin() ? $GLOBALS['plugin_page_cr'] : '';
  673. if ( is_preview() || in_array( $GLOBALS['pagenow'], $revision_uris ) || in_array( $plugin_page, $revision_uris ) ) {
  674. $strip_capreqs = array();
  675. foreach( (array) $object_type as $_object_type ) {
  676. if ( $type_obj = get_post_type_object( $_object_type ) ) {
  677. $strip_capreqs = array_merge( $strip_capreqs, array( $type_obj->cap->edit_published_posts, $type_obj->cap->edit_private_posts ) );
  678. if ( array_intersect( $reqd_caps, $strip_capreqs ) )
  679. $reqd_caps []= $type_obj->cap->edit_posts;
  680. }
  681. }
  682. $reqd_caps = array_unique( array_diff($reqd_caps, $strip_capreqs) );
  683. }
  684. $do_revision_clause = true;
  685. }
  686. }
  687. if ( ! is_object($user) ) { // TODO: can we skip this now that user is a required arg?
  688. $user = $GLOBALS['current_rs_user'];
  689. }
  690. if ( ! $src = $this->scoper->data_sources->get($src_name) ) {
  691. rs_notice ( sprintf( 'Role Scoper Config Error (%1$s): Data source (%2$s) is not defined.', 'objects_where_role_clauses', $src_name) );
  692. return ' 1=2 ';
  693. }
  694. $src_table = ( ! empty($source_alias) ) ? $source_alias : $src->table;
  695. // special case to include pending / scheduled revisions by object role
  696. if ( ! isset( $args['objrole_revisions_clause'] ) ) {
  697. $args['objrole_revisions_clause'] = ( 'edit.php' == $GLOBALS['pagenow'] );
  698. }
  699. // These arguments are simply passed on to objects_where_scope_clauses()
  700. if ( 'group' == $src_name )
  701. $args['otype_use_object_roles'] = true;
  702. elseif ( ! empty($src->no_object_roles) )
  703. $args['otype_use_object_roles'] = false;
  704. if ( $args['otype_use_object_roles'] ) {
  705. // Return all object_ids that require any role to be object-assigned
  706. // We will use an ID NOT IN clause so these are not satisfied by blog/term role assignment
  707. $args['objscope_objects'] = $this->scoper->get_restrictions(OBJECT_SCOPE_RS, $src_name);
  708. }
  709. $where = array();
  710. foreach ( $reqd_caps as $cap_name ) {
  711. // If supporting custom user blogcaps, a separate role clause for each cap
  712. // Otherwise (default) all reqd_caps from one role assignment (whatever scope it may be)
  713. if ( $custom_user_blogcaps ) {
  714. $reqd_caps_arg = array($cap_name);
  715. } else {
  716. $reqd_caps_arg = $reqd_caps;
  717. $cap_name = '';
  718. }
  719. $qualifying_roles = $this->scoper->role_defs->qualify_roles($reqd_caps_arg, '', $object_type );
  720. /*
  721. rs_errlog( '' );
  722. rs_errlog( "reqd_caps arg: " . serialize($reqd_caps_arg) );
  723. rs_errlog( "qualifying roles for $object_type: " . serialize($qualifying_roles) );
  724. rs_errlog( '' );
  725. */
  726. if ( $alternate_reqd_caps && is_array( $alternate_reqd_caps ) ) { // $alternate_reqd_caps[setnum] = array of cap_names
  727. foreach ( $alternate_reqd_caps as $alternate_capset ) {
  728. foreach ( $alternate_capset as $alternate_reqd_caps ) {
  729. if ( $alternate_roles = $this->scoper->role_defs->qualify_roles($alternate_reqd_caps) )
  730. $qualifying_roles = array_merge($qualifying_roles, $alternate_roles);
  731. }
  732. }
  733. }
  734. // this is needed mainly for the chicken-egg situation of uploading a file into a post before a category is stored, when editing rights are based on category
  735. //if ( $args['otype_use_object_roles'] )
  736. $args['ignore_restrictions'] = ( 1 == count($reqd_caps_arg) ) && $this->scoper->cap_defs->member_property( reset($reqd_caps_arg), 'ignore_restrictions' );
  737. if ( $owner_reqd_caps = $this->scoper->cap_defs->get_base_caps($reqd_caps_arg) ) {
  738. $owner_roles = ( $require_full_object_role ) ? $qualifying_roles : $this->scoper->role_defs->qualify_roles($owner_reqd_caps, '', $object_type);
  739. if ( ! empty($alternate_roles ) )
  740. $owner_roles = array_merge( $owner_roles, $alternate_roles );
  741. } else
  742. $owner_roles = array();
  743. // have to pass qualifying_object_roles in for 'user' call because qualifying_roles may not include a qualifying object role (i.e. Page Contributor object role assignment)
  744. if ( $owner_roles && ( empty( $GLOBALS['revisionary'] ) || empty( $GLOBALS['revisionary']->skip_revision_allowance ) ) )
  745. $qualifying_object_roles = $this->scoper->confirm_object_scope( $owner_roles );
  746. else
  747. $qualifying_object_roles = $this->scoper->confirm_object_scope( $qualifying_roles ); // get_base_caps() strips out edit_private_* cap requirement for post owner, in compliance with WP metacap mapping. But for Revisionary, that causes Revisors to have full editing caps if a page is privately published (but not if it's publicly published).
  748. if ( $qualifying_roles || ! empty($qualifying_object_roles) ) {
  749. //d_echo( "regular objects_where_scope_clauses for " . serialize( $reqd_caps ) );
  750. $args = array_merge( $args, compact( 'qualifying_roles', 'qualifying_object_roles' ) );
  751. $where[$cap_name]['user'] = $this->objects_where_scope_clauses($src_name, $reqd_caps_arg, $args );
  752. }
  753. if ( ! empty($src->cols->owner) && ! $skip_owner_clause && $user->ID ) {
  754. if ( ! $require_full_object_role ) {
  755. // if owner qualifies for the operation by any different roles than other users, add separate owner clause
  756. $src_table = ( ! empty($source_alias) ) ? $source_alias : $src->table;
  757. if ( ! $owner_reqd_caps ) {
  758. // all reqd_caps are granted to owner automatically
  759. $where[$cap_name]['owner'] = "$src_table.{$src->cols->owner} = '$user->ID'";
  760. } elseif ( $owner_reqd_caps != $reqd_caps_arg ) {
  761. if ( $owner_roles ) {
  762. //d_echo( "owner objects_where_scope_clauses: " );
  763. $args = array_merge($args, array( 'qualifying_roles' => $owner_roles ) );
  764. $scope_temp = $this->objects_where_scope_clauses($src_name, $owner_reqd_caps, $args );
  765. if ( ( $scope_temp != $where[$cap_name]['user'] ) && ! is_null($scope_temp) ) { // TODO: why is null ever returned?
  766. $parent_clause = '';
  767. // enable authors to view / edit / approve revisions to their published posts
  768. if ( ! empty( $do_revision_clause ) && ! defined( 'HIDE_REVISIONS_FROM_AUTHOR' ) ) {
  769. static $owner_ids = array();
  770. if ( ! isset( $owner_ids[$user->ID][$object_type] ) ) { // also keying by user ID in case this filter is invoked for a non-current user
  771. $type_clause = ( ! empty($src->cols->type) ) ? "{$src->cols->type} = '$object_type' AND" : '';
  772. $owner_ids[$user->ID][$object_type] = scoper_get_col( "SELECT {$src->cols->id} FROM $src->table WHERE $type_clause {$src->cols->owner} = '$user->ID'" );
  773. }
  774. if ( ! empty($src->cols->type) && ! empty($src->cols->parent) && $owner_ids[$user->ID][$object_type] )
  775. $parent_clause = "OR $src_table.{$src->cols->type} = 'revision' AND $src_table.{$src->cols->parent} IN ('" . implode( "','", $owner_ids[$user->ID][$object_type] ) . "')";
  776. }
  777. $where[$cap_name]['owner'] = '( ' . $scope_temp . " ) AND ( $src_table.{$src->cols->owner} = '$user->ID' $parent_clause )";
  778. }
  779. }
  780. }
  781. }
  782. }
  783. // all role clauses concat: user clauses [OR] [owner clauses]
  784. if ( ! empty($where[$cap_name]) )
  785. $where[$cap_name] = agp_implode(' ) OR ( ', $where[$cap_name], ' ( ', ' ) ');
  786. // if not supporting custom caps, we actually passed all reqd_caps in first iteration
  787. if ( ! $custom_user_blogcaps )
  788. break;
  789. }
  790. // all reqd caps concat: cap1 clauses [AND] [cap2 clauses] [AND] ...
  791. if ( ! empty($where) )
  792. $where = agp_implode(' ) AND ( ', $where, ' ( ', ' ) ');
  793. else
  794. return '1=2';
  795. return $where;
  796. }
  797. function objects_where_scope_clauses($src_name, $reqd_caps, $args ) {
  798. // Optional Args (will be defaulted to meaningful values)
  799. // Note: ignore_restrictions affects Scoper::qualify_terms() output
  800. $defaults = array( 'taxonomies' => '', 'use_blog_roles' => true, 'terms_query' => false, 'qualifying_object_roles' => false,
  801. 'skip_objscope_check' => false, 'require_full_object_role' => false, 'objrole_revisions_clause' => false, 'ignore_restrictions' => false );
  802. // Required Args
  803. // NOTE: use_object_roles is a boolean for the single object_type in question, but use_term_roles is array[taxonomy] = true or false
  804. $required = array_fill_keys( array( 'user', 'object_type', 'qualifying_roles', 'otype_use_term_roles', 'otype_use_object_roles' ), true );
  805. if ( $missing = array_diff_key( $required, $args ) ) {
  806. rs_notice ( sprintf( 'Role Scoper Runtime Error (%1$s) - Missing argument(s): %2$s', 'objects_where_scope_clauses', implode( ", ", array_keys($missing) ) ) );
  807. return ' 1=2 ';
  808. }
  809. $defaults = array_merge( $defaults, $required );
  810. $args = array_merge( $defaults, (array) $args );
  811. extract($args);
  812. if ( ! $src = $this->scoper->data_sources->get($src_name) ) {
  813. rs_notice ( sprintf( 'Role Scoper Config Error (%1$s): Data source (%2$s) is not defined.', 'objects_where_scope_clauses', $src_name ) );
  814. return ' 1=2 ';
  815. }
  816. $src_table = ( ! empty($source_alias) ) ? $source_alias : $src->table;
  817. if ( 'group' == $src_name )
  818. $otype_use_object_roles = true;
  819. elseif ( ! empty($src->no_object_roles) )
  820. $otype_use_object_roles = false;
  821. // primary qualifying_roles array should contain only RS roles
  822. $qualifying_roles = $this->scoper->role_defs->filter( $qualifying_roles, array( 'role_type' => 'rs' ), 'names_as_key' );
  823. if ( $otype_use_object_roles ) {
  824. // For object assignment, replace any "others" reqd_caps array.
  825. // Also exclude any roles which have never been assigned to any object
  826. if ( ! is_array( $qualifying_object_roles ) )
  827. $qualifying_object_roles = $this->scoper->confir

Large files files are truncated, but you can click here to view the full file