Refactoring : sécurité (XSS), découpage en modules inc/* et js/admin/*, IDs résolus par slug, perf (caches, cron Gravatar, assets auto-hébergés), tests
This commit is contained in:
233
inc/access-control.php
Normal file
233
inc/access-control.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/**
|
||||
* Restrictions d'accès aux contenus et capacités des contributeurs :
|
||||
* - liste admin des contributeurs limitée à leurs posts + posts où ils
|
||||
* figurent en membres/autre_membres (et compteurs cohérents) ;
|
||||
* - droit d'édition par post pour les membres listés (user_has_cap) ;
|
||||
* - catégorie « Vie du labo » réservée aux connectés ;
|
||||
* - redirections login/dashboard des non-admins.
|
||||
*/
|
||||
|
||||
// Restrict Contributors to see only their own posts in admin,
|
||||
// but also include posts where they appear in membres/autre_membres.
|
||||
function restrict_contributor_posts( $query ) {
|
||||
if ( ! is_admin() || ! $query->is_main_query() || current_user_can( 'edit_others_posts' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $user_ID, $wpdb;
|
||||
|
||||
// Posts where the user is listed as membre or autre_membre.
|
||||
$membre_ids = array_map( 'intval', (array) $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT DISTINCT post_id FROM {$wpdb->postmeta}
|
||||
WHERE meta_key IN ('membres', 'autre_membres') AND meta_value = %s",
|
||||
$user_ID
|
||||
)
|
||||
) );
|
||||
|
||||
if ( empty( $membre_ids ) ) {
|
||||
// Fast path: no membre posts, use simple author filter.
|
||||
$query->set( 'author', $user_ID );
|
||||
return;
|
||||
}
|
||||
|
||||
// Posts authored by this user.
|
||||
$authored_ids = array_map( 'intval', (array) $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM {$wpdb->posts} WHERE post_author = %d",
|
||||
$user_ID
|
||||
)
|
||||
) );
|
||||
|
||||
$all_ids = array_unique( array_merge( $authored_ids, $membre_ids ) );
|
||||
|
||||
// post__in with [0] returns nothing when the combined set is empty.
|
||||
$query->set( 'post__in', empty( $all_ids ) ? [ 0 ] : $all_ids );
|
||||
}
|
||||
add_action( 'pre_get_posts', 'restrict_contributor_posts' );
|
||||
|
||||
/**
|
||||
* Let contributors listed as membres/autre_membres on a post edit it.
|
||||
*
|
||||
* user_has_cap modifies capabilities only for this single check —
|
||||
* it does not permanently alter the user's capability set.
|
||||
*/
|
||||
function thalim_membres_can_edit_post( $allcaps, $caps, $args, $user ) {
|
||||
// Editors and above already have edit_others_posts — nothing to do.
|
||||
if ( ! empty( $allcaps['edit_others_posts'] ) ) {
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
if ( empty( $args[0] ) ) {
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
$cap = $args[0];
|
||||
|
||||
// Meta caps that carry a post ID in $args[2] (e.g. wp-admin/post.php load).
|
||||
$meta_caps_with_id = [ 'edit_post', 'edit_page' ];
|
||||
|
||||
// Primitive caps called during the admin save flow *without* a
|
||||
// post_id (e.g. wp-admin/includes/post.php:76 checks edit_others_posts
|
||||
// directly when $post_author !== current user). We infer the post_id from
|
||||
// the request so we can still authorize membres per-post.
|
||||
//
|
||||
// NOTE: publish_posts / publish_pages are intentionally NOT in this list —
|
||||
// contributors listed in `membres` must be able to edit (incl. published)
|
||||
// posts of the lab, but only editors/admins should be able to publish.
|
||||
$primitive_caps_in_save_flow = [
|
||||
'edit_others_posts',
|
||||
'edit_others_pages',
|
||||
'edit_published_posts',
|
||||
'edit_published_pages',
|
||||
];
|
||||
|
||||
$post_id = 0;
|
||||
if ( in_array( $cap, $meta_caps_with_id, true ) && ! empty( $args[2] ) ) {
|
||||
$post_id = (int) $args[2];
|
||||
} elseif ( in_array( $cap, $primitive_caps_in_save_flow, true ) ) {
|
||||
$post_id = (int) ( $_POST['post_ID'] ?? $_REQUEST['post'] ?? 0 );
|
||||
}
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
$user_id = $user->ID;
|
||||
|
||||
$membre_ids = array_map(
|
||||
'intval',
|
||||
array_merge(
|
||||
(array) get_post_meta( $post_id, 'membres', false ),
|
||||
(array) get_post_meta( $post_id, 'autre_membres', false )
|
||||
)
|
||||
);
|
||||
|
||||
if ( in_array( $user_id, $membre_ids, true ) ) {
|
||||
// Grant every primitive cap mapped for this check
|
||||
// (e.g. edit_others_posts, edit_published_posts).
|
||||
foreach ( $caps as $c ) {
|
||||
$allcaps[ $c ] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $allcaps;
|
||||
}
|
||||
add_filter( 'user_has_cap', 'thalim_membres_can_edit_post', 10, 4 );
|
||||
|
||||
// Prevent WP_Posts_List_Table from auto-redirecting contributors to the "Mine"
|
||||
// view. The constructor sets $_GET['author'] = current_user_id() when the user
|
||||
// lacks edit_others_posts and no other filter is active. Setting all_posts=1
|
||||
// before the list table is constructed short-circuits that condition.
|
||||
add_action( 'load-edit.php', function () {
|
||||
if ( current_user_can( 'edit_others_posts' ) ) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
empty( $_REQUEST['post_status'] )
|
||||
&& empty( $_REQUEST['all_posts'] )
|
||||
&& empty( $_REQUEST['author'] )
|
||||
&& empty( $_REQUEST['show_sticky'] )
|
||||
) {
|
||||
$_GET['all_posts'] = 1;
|
||||
$_REQUEST['all_posts'] = 1;
|
||||
}
|
||||
} );
|
||||
|
||||
// Adjust post-status counts so contributors see only posts they can access
|
||||
// (posts they authored + posts listed in membres/autre_membres), not all posts.
|
||||
// The wp_count_posts filter runs even on cached values, so it won't pollute
|
||||
// the shared cache.
|
||||
add_filter( 'wp_count_posts', function ( $counts, $type, $perm ) {
|
||||
if ( ! is_admin() || current_user_can( 'edit_others_posts' ) ) {
|
||||
return $counts;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT post_status, COUNT(*) AS num_posts
|
||||
FROM {$wpdb->posts}
|
||||
WHERE post_type = %s
|
||||
AND ID IN (
|
||||
SELECT ID FROM {$wpdb->posts}
|
||||
WHERE post_author = %d AND post_type = %s
|
||||
UNION
|
||||
SELECT post_id FROM {$wpdb->postmeta}
|
||||
WHERE meta_key IN ('membres', 'autre_membres') AND meta_value = %s
|
||||
)
|
||||
GROUP BY post_status",
|
||||
$type,
|
||||
$user_id,
|
||||
$type,
|
||||
(string) $user_id
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$new_counts = array_fill_keys( get_post_stati(), 0 );
|
||||
foreach ( $results as $row ) {
|
||||
$new_counts[ $row['post_status'] ] = (int) $row['num_posts'];
|
||||
}
|
||||
|
||||
return (object) $new_counts;
|
||||
}, 10, 3 );
|
||||
|
||||
// ── "Vie du labo" — restricted to logged-in users ────
|
||||
add_action( 'pre_get_posts', function( $query ) {
|
||||
if ( is_user_logged_in() ) return;
|
||||
$vie_du_labo = thalim_cat_id( 'vie-du-labo' );
|
||||
if ( ! $vie_du_labo ) return;
|
||||
$excluded = $query->get( 'category__not_in' );
|
||||
if ( ! is_array( $excluded ) ) $excluded = $excluded ? [ $excluded ] : [];
|
||||
if ( ! in_array( $vie_du_labo, $excluded ) ) {
|
||||
$excluded[] = $vie_du_labo;
|
||||
$query->set( 'category__not_in', $excluded );
|
||||
}
|
||||
} );
|
||||
|
||||
add_action( 'template_redirect', function() {
|
||||
if ( ! is_user_logged_in() && is_category( thalim_cat_id( 'vie-du-labo' ) ) ) {
|
||||
wp_safe_redirect( home_url( '/' ) );
|
||||
exit;
|
||||
}
|
||||
} );
|
||||
|
||||
add_filter( 'wp_nav_menu_objects', function( $items, $args ) {
|
||||
if ( is_user_logged_in() ) return $items;
|
||||
$vie_du_labo = thalim_cat_id( 'vie-du-labo' );
|
||||
return array_values( array_filter( $items, function( $item ) use ( $vie_du_labo ) {
|
||||
if ( $item->object === 'category' && (int) $item->object_id === $vie_du_labo ) return false;
|
||||
if ( strpos( $item->url, 'vie-du-labo' ) !== false ) return false;
|
||||
return true;
|
||||
} ) );
|
||||
}, 10, 2 );
|
||||
|
||||
// Non-admins: hide dashboard and tools menu, redirect to posts list
|
||||
add_action( 'admin_menu', function() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
remove_menu_page( 'tools.php' );
|
||||
remove_menu_page( 'index.php' );
|
||||
}
|
||||
} );
|
||||
|
||||
// Redirect non-admins away from dashboard to their posts list
|
||||
add_action( 'admin_init', function() {
|
||||
if ( current_user_can( 'manage_options' ) ) return;
|
||||
global $pagenow;
|
||||
if ( $pagenow === 'index.php' ) {
|
||||
wp_safe_redirect( admin_url( 'edit.php' ) );
|
||||
exit;
|
||||
}
|
||||
} );
|
||||
|
||||
// After login, send non-admins to posts list instead of dashboard
|
||||
add_filter( 'login_redirect', function( $redirect_to, $requested, $user ) {
|
||||
if ( ! is_wp_error( $user ) && ! $user->has_cap( 'manage_options' ) ) {
|
||||
return admin_url( 'edit.php' );
|
||||
}
|
||||
return $redirect_to;
|
||||
}, 10, 3 );
|
||||
Reference in New Issue
Block a user