<?php
/**
 * Delegated user management for Marketing (without full admin).
 *
 * Erin (Marketing) needs to run the site's people: create new teacher accounts,
 * change roles (e.g. promote a teacher to Site Editor), and link users to their
 * staff profile — WITHOUT being a full administrator.
 *
 * WordPress bundles user management into caps that normally only Administrators
 * hold, and handing them out naively is a privilege-escalation risk: a non-admin
 * who can edit users could make themselves admin or hijack an admin account. So
 * this grants the specific user-management caps to designated people, fenced by
 * two hard guardrails that ALWAYS apply to non-admins:
 *
 *   1. editable_roles — a non-admin may assign ANY role EXCEPT administrator, so
 *      they can never create or promote an admin. WordPress validates a
 *      submitted role against this list on save, so it covers the real save
 *      path, not just the dropdown UI.
 *   2. map_meta_cap — a non-admin can never edit, delete or re-role an
 *      administrator account, so they can't hijack yours or support's.
 *
 * Net effect: a manager runs the teacher/editor roster; administrators stay
 * exclusively an admin concern.
 *
 * The delegated caps are granted to the EDITOR ROLE (see bw_roles_register in
 * inc/bw-roles.php) — Rian's model is "Editor = Administrator without the
 * clutter", so every Editor can manage the roster. The guardrails below apply
 * to ALL non-admins, so that stays safe regardless of who is an Editor.
 *
 * @package Kadence-Child
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/** The user-management capabilities delegated to a non-admin manager. */
function bw_um_manager_caps() {
	return array( 'list_users', 'create_users', 'edit_users', 'delete_users', 'promote_users' );
}

/**
 * A non-admin who holds the delegated manager caps. Used only for messaging /
 * checks — the guardrails below apply to ALL non-admins regardless.
 */
function bw_um_is_delegated_manager( $user = null ) {
	$user = $user ? $user : wp_get_current_user();
	if ( ! $user || ! $user->exists() || user_can( $user, 'manage_options' ) ) {
		return false;
	}
	return user_can( $user, 'list_users' ) && user_can( $user, 'promote_users' );
}

/**
 * Guardrail 1 — non-admins can assign every role EXCEPT administrator.
 *
 * Applies to the role dropdowns on add-new / edit-user AND to WordPress's
 * save-time validation (core rejects a submitted role that isn't in
 * get_editable_roles()). So a delegated manager cannot create or promote an
 * administrator by any normal path.
 */
add_filter(
	'editable_roles',
	function ( $roles ) {
		if ( current_user_can( 'manage_options' ) ) {
			return $roles;
		}
		unset( $roles['administrator'] );
		return $roles;
	}
);

/**
 * Guardrail 2 — a non-admin can never edit / delete / re-role an administrator.
 *
 * Protects every administrator account (yours, support) from takeover by a
 * delegated manager who might otherwise change an admin's email or password.
 */
add_filter(
	'map_meta_cap',
	function ( $caps, $cap, $user_id, $args ) {
		if ( ! in_array( $cap, array( 'edit_user', 'delete_user', 'promote_user', 'remove_user' ), true ) ) {
			return $caps;
		}
		$actor = get_userdata( (int) $user_id );
		if ( ! $actor || user_can( $actor, 'manage_options' ) ) {
			return $caps; // real admins are unaffected
		}
		$target_id = isset( $args[0] ) ? (int) $args[0] : 0;
		// Acting on someone else who is an administrator → deny outright.
		if ( $target_id && $target_id !== (int) $user_id && user_can( $target_id, 'manage_options' ) ) {
			return array( 'do_not_allow' );
		}
		return $caps;
	},
	10,
	4
);

/**
 * Keep a staff profile's AUTHOR in sync with its linked user.
 *
 * /my-profile authorises editing by the `staff_user_id` LINK (ownership), while
 * a Site Editor's ability to edit a post is governed by AUTHORSHIP. Keeping the
 * two in sync means that when a teacher is linked to their profile — and later
 * promoted to Site Editor — they can still edit that profile. This fires for the
 * admin "Connect User Account" field AND the front-end manager link (both go
 * through bw_profile_set() -> update_field()).
 */
add_filter(
	'acf/update_value/key=field_bw_staff_user',
	function ( $value, $post_id ) {
		$post_id = (int) $post_id;
		$uid     = is_array( $value ) ? (int) reset( $value ) : (int) $value;
		if ( $post_id && 'staff' === get_post_type( $post_id ) && $uid && get_userdata( $uid ) ) {
			$post = get_post( $post_id );
			if ( $post && (int) $post->post_author !== $uid ) {
				global $wpdb;
				// Column-only update — never wp_update_post (would re-save content).
				$wpdb->update( $wpdb->posts, array( 'post_author' => $uid ), array( 'ID' => $post_id ) );
				clean_post_cache( $post_id );
			}
		}
		return $value;
	},
	20,
	2
);
