* @package View_Admin_As */ if ( ! defined( 'VIEW_ADMIN_AS_DIR' ) ) { die(); } /** * View handler class. * * @author Jory Hogeveen * @package View_Admin_As * @since 1.6 * @since 1.7 Class got split up: data handling/updating is now in VAA_View_Admin_As_Controller. * @version 1.8 * @uses \VAA_View_Admin_As_Base Extends class */ final class VAA_View_Admin_As_View extends VAA_View_Admin_As_Base { /** * The single instance of the class. * * @since 1.6 * @static * @var \VAA_View_Admin_As_View */ private static $_instance = null; /** * Is the current user modified? * * @since 1.7.2 * @var bool */ private $is_user_modified = false; /** * VAA_View_Admin_As_View constructor. * * @since 1.6 * @since 1.6.1 $vaa param. * @access protected * @param \VAA_View_Admin_As $vaa The main VAA object. */ protected function __construct( $vaa ) { self::$_instance = $this; parent::__construct( $vaa ); } /** * Initializes after VAA is enabled. * * @since 1.6 * @access public * @return void */ public function init() { if ( $this->store->get_view() ) { $this->do_view(); } } /** * Apply view data. * * @since 1.6.3 Put logic in it's own function. * @access private * @return void */ private function do_view() { // @since 1.6.4 Set the current user as the selected user by default. $this->store->set_selectedUser( $this->store->get_curUser() ); $this->store->set_selectedCaps( $this->store->get_curUser()->allcaps ); /** * VISITOR. * Current user object views (switches current user). * * @since 1.6.2 Visitor view. */ if ( $this->store->get_view( 'visitor' ) ) { /** * Change current user object so changes can be made on various screen settings. * wp_set_current_user() returns the new user object. * * If it is a visitor view it will convert the false return from 'user' to 0. */ $this->store->set_selectedUser( wp_set_current_user( 0 ) ); // @since 1.6.2 Set the caps for this view (user view). if ( isset( $this->store->get_selectedUser()->allcaps ) ) { $this->store->set_selectedCaps( $this->store->get_selectedUser()->allcaps ); } } /** * View data is set, apply the view. * This hook can be used by other modules to enable a view. * * Temporary modifications to the current user are set on priority 99. * This functionality has a separate action: `vaa_view_admin_as_modify_current_user`. * * @since 1.6.3 * @param array */ do_action( 'vaa_view_admin_as_do_view', $this->store->get_view() ); /** * Force own locale on view. * * @since 1.6.1 * @since 1.7.5 Add filter `view_admin_as_freeze_locale`. * * @param bool $freeze_locale The user setting. * @return bool */ $freeze_locale = apply_filters( 'view_admin_as_freeze_locale', $this->store->get_userSettings( 'freeze_locale' ) ); if ( $freeze_locale && (int) $this->store->get_curUser()->ID !== (int) $this->store->get_selectedUser()->ID ) { $this->add_action( 'after_setup_theme', array( $this, 'freeze_locale' ), 0 ); } } /** * Adds the actions and filters to modify the current user object. * Can only be run once. * * @since 1.6.3 * @access public * @return void */ public function init_user_modifications() { static $done; if ( $done ) return; $this->is_user_modified = true; $this->add_action( 'vaa_view_admin_as_do_view', array( $this, 'modify_user' ), 99 ); /** * Make sure the $current_user view data isn't overwritten again by switch_blog functions. * @see This filter is documented in wp-includes/ms-blogs.php * @since 1.6.3 */ $this->add_action( 'switch_blog', array( $this, 'modify_user' ) ); /** * Prevent some meta updates for the current user while in modification to the current user are active. * @since 1.6.3 */ $this->add_filter( 'update_user_metadata' , array( $this, 'filter_prevent_update_user_metadata' ), 999999999, 3 ); /** * Get capabilities and user level from current user view object instead of database. * @since 1.6.4 */ $this->add_filter( 'get_user_metadata' , array( $this, 'filter_overrule_get_user_metadata' ), 999999999, 3 ); // `user_has_cap` priority. $priority = -999999999; if ( $this->store->get_view( 'caps' ) ) { // Overwrite everything when the capability view is active. remove_all_filters( 'user_has_cap' ); $priority = 999999999; } /** * The priority value of the VAA `user_has_cap` filter. * Runs as first by default. * * @since 1.7.2 * @param int $priority * @return int */ $priority = (int) apply_filters( 'view_admin_as_user_has_cap_priority', $priority ); /** * Change the capabilities. * * @since 1.7.1 * @since 1.7.2 Changed priority to set is at the beginning instead of as last * to allow other plugins to filter based on the modified user. */ $this->add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), $priority, 4 ); /** * Map the capabilities (map_meta_cap is used for compatibility with network admins). * Filter as last to check other plugin changes as well. * * @since 0.1 */ $this->add_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 999999999, 4 ); /** * Disable super admin status for the current user. * @since 1.7.3 * @since 1.8 Check for multisite. */ if ( is_multisite() && ! is_network_admin() && is_super_admin( $this->store->get_selectedUser()->ID ) && $this->store->get_userSettings( 'disable_super_admin' ) ) { $this->disable_super_admin(); } $done = true; } /** * Update the current user's WP_User instance with the current view capabilities. * * @since 1.6.3 * @access public * @return void */ public function modify_user() { // Can be the current or selected WP_User object (depending on the user view). $user = $this->store->get_selectedUser(); /** * Allow other modules to hook after the initial changes to the current user. * * @since 1.6.3 * @since 1.6.4 Changed name (was: `vaa_view_admin_as_modify_current_user`). * @param \WP_User $user The modified user object. */ do_action( 'vaa_view_admin_as_modify_user', $user ); } /** * Prevent some updates to the current user like roles and capabilities. * to prevent problems when making changes within a view. * * IMPORTANT! This filter should ONLY be used when a view is selected! * * @since 1.6.3 * @access public * @see init_current_user_modifications() * * @see 'update_user_metadata' filter * @link https://codex.wordpress.org/Plugin_API/Filter_Reference/update_(meta_type)_metadata * @link http://hookr.io/filters/update_user_metadata/ * * @global \wpdb $wpdb * @param null $null Whether to allow updating metadata for the given type. * @param int $object_id Object ID. * @param string $meta_key Meta key. * @return mixed */ public function filter_prevent_update_user_metadata( $null, $object_id, $meta_key ) { global $wpdb; $user = $this->store->get_selectedUser(); // Check if the object being updated is the current user. if ( (int) $user->ID === (int) $object_id ) { // Capabilities meta key check. if ( empty( $user->cap_key ) ) { $user->cap_key = $wpdb->get_blog_prefix() . 'capabilities'; } // Do not update the current user capabilities or user level while in a view. if ( in_array( $meta_key, array( $user->cap_key, $wpdb->get_blog_prefix() . 'capabilities', $wpdb->get_blog_prefix() . 'user_level', ), true ) ) { return false; } } return $null; } /** * Return view roles when getting the current user data to prevent reloading current user data within a view. * * IMPORTANT! This filter should ONLY be used when a view is selected! * * @since 1.6.4 * @access public * @see init_current_user_modifications() * * @see 'get_user_metadata' filter * @link https://codex.wordpress.org/Plugin_API/Filter_Reference/get_(meta_type)_metadata * * @global \wpdb $wpdb * @param null $null The value update_metadata() should return. * @param int $object_id Object ID. * @param string $meta_key Meta key. * @return mixed */ public function filter_overrule_get_user_metadata( $null, $object_id, $meta_key ) { global $wpdb; $user = $this->store->get_selectedUser(); // Check if the object being updated is the current user. if ( (int) $user->ID === (int) $object_id ) { // Return the current user capabilities or user level while in a view. // Always return an array to fix $single usage. // Current user cap key should be equal to the meta_key for capabilities. if ( ! empty( $user->cap_key ) && $meta_key === $user->cap_key ) { return array( $user->caps ); } // Fallback if cap_key doesn't exists. if ( $meta_key === $wpdb->get_blog_prefix() . 'capabilities' ) { return array( $user->caps ); } if ( $meta_key === $wpdb->get_blog_prefix() . 'user_level' ) { if ( ! isset( $user->user_level ) ) { // Make sure the key exists. Result will be filtered in `filter_prevent_update_user_metadata()`. $user->update_user_level_from_caps(); } return array( $user->user_level ); } } return $null; } /** * Change capabilities when the user has selected a view. * If the capability isn't in the chosen view, then make the value for this capability empty and add "do_not_allow". * * @since 0.1 * @since 1.5 Changed function name to map_meta_cap (was change_caps). * @since 1.6 Moved to this class from main class. * @since 1.6.2 Use logic from current_view_can(). * @since 1.6.3 Prefix function name with `filter_`. * @since 1.7.2 Use the `user_has_cap` filter for compatibility enhancements. * @access public * * @param array $caps The actual (mapped) cap names, if the caps are not mapped this returns the requested cap. * @param string $cap The capability that was requested. * @param int $user_id The ID of the user. * @param array $args Adds the context to the cap. Typically the object ID (not used). * @return array $caps */ public function filter_map_meta_cap( $caps, $cap, $user_id, $args = array() ) { if ( (int) $this->store->get_selectedUser()->ID !== (int) $user_id ) { return $caps; } $filter_caps = (array) $this->store->get_selectedCaps(); if ( ! $this->store->get_view( 'caps' ) ) { /** * Apply user_has_cap filters to make sure we are compatible with modifications from other plugins. * * Issues found: * - Restrict User Access - Overwrites our filtered capabilities. (fixed since RUA 0.15.x). * - Groups - Overwrites our filtered capabilities. (fixed in Groups module). * * @since 1.7.2 * @see \WP_User::has_cap() */ $filter_caps = apply_filters( 'user_has_cap', $filter_caps, $caps, // Replicate arguments for `user_has_cap`. array_merge( array( $cap, $user_id ), (array) $args ), $this->store->get_selectedUser() ); } foreach ( (array) $caps as $actual_cap ) { if ( ! $this->current_view_can( $actual_cap, $filter_caps ) ) { // Regular users. Assuming this capability never exists.. $caps['vaa_do_not_allow'] = 'vaa_do_not_allow'; // Network admins. $caps['do_not_allow'] = 'do_not_allow'; } } return $caps; } /** * Overwrite the user's capabilities. * * @since 1.6.3 * @access public * * @param array $allcaps All the capabilities of the user. * @param array $caps Actual capabilities for meta capability. * @param array $args [0] Requested capability. * [1] User ID. * [2] Associated object ID. * @param \WP_User $user (WP 3.7+) The user object. * @return array */ public function filter_user_has_cap( $allcaps, $caps, $args, $user = null ) { $user_id = ( $user ) ? $user->ID : $args[1]; if ( is_numeric( $user_id ) && (int) $user_id === (int) $this->store->get_selectedUser()->ID ) { return (array) $this->store->get_selectedCaps(); } return $allcaps; } /** * Remove the current user from the list of super admins. * This sets/changes the global $super_admins variable which overwrites the site option. * * @since 1.7.3 * @access public * @see grant_super_admin() >> wp-includes/capabilities.php * @see revoke_super_admin() >> wp-includes/capabilities.php * @see get_super_admins() >> wp-includes/capabilities.php * @see is_super_admin() >> wp-includes/capabilities.php * @link https://developer.wordpress.org/reference/functions/is_super_admin/ * * @global array $super_admins * @param \WP_User|int|string $user (optional) A user to remove. Both a user object or a user field is accepted. * @param string $field (optional) A user field key to get the user data by. */ public function disable_super_admin( $user = null, $field = 'id' ) { global $super_admins; if ( ! isset( $super_admins ) ) { $super_admins = get_super_admins(); } $user = ( null !== $user ) ? $user : $this->store->get_selectedUser(); if ( ! $user instanceof WP_User ) { $user = get_user_by( $field, $user ); } // Remove current user from the super admins array. // Effectively disables functions grant_super_admin() and revoke_super_admin(). if ( ! empty( $user->user_login ) && is_array( $super_admins ) ) { $key = array_search( $user->user_login, $super_admins, true ); if ( false !== $key ) { unset( $super_admins[ $key ] ); $GLOBALS['super_admins'] = $super_admins; } } } /** * Similar function to current_user_can(). * * @since 1.6.2 * @since 1.8 Check for non-scalar types being passed as first parameter. * @access public * * @param string $cap The capability. * @param array $caps (optional) Capabilities to compare to. * Defaults to the selected caps for the current view. * @return bool */ public function current_view_can( $cap, $caps = array() ) { if ( empty( $caps ) ) { $caps = $this->store->get_selectedCaps(); } if ( ! is_scalar( $cap ) ) { $cap = (array) $cap; foreach ( $cap as $capability ) { if ( ! $this->current_view_can( $capability, $caps ) ) { return false; } } return true; } $cap = (string) $cap; if ( is_array( $caps ) && ! empty( $caps[ $cap ] ) && 'do_not_allow' !== $cap && 'do_not_allow' !== $caps[ $cap ] ) { return true; } return false; } /** * Is the current user modified? * * @since 1.7.2 * @access public * @return bool */ public function is_user_modified() { return (bool) $this->is_user_modified; } /** * Set the locale for the current view. * * @since 1.6.1 * @access public * @return bool Will return false when used with older WP versions. */ public function freeze_locale() { if ( function_exists( 'get_user_locale' ) && function_exists( 'switch_to_locale' ) ) { $locale = get_user_locale( $this->store->get_curUser()->ID ); if ( get_locale() !== $locale ) { switch_to_locale( $locale ); } return true; } return false; } /** * Main Instance. * * Ensures only one instance of this class is loaded or can be loaded. * * @since 1.6 * @access public * @static * @param \VAA_View_Admin_As $caller The referrer class. * @return \VAA_View_Admin_As_View $this */ public static function get_instance( $caller = null ) { if ( is_null( self::$_instance ) ) { self::$_instance = new self( $caller ); } return self::$_instance; } } // End class VAA_View_Admin_As_View.