JoryHogeveen/view-admin-as

View on GitHub
modules/class-groups.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php
/**
 * View Admin As - Groups plugin
 *
 * @author  Jory Hogeveen <info@keraweb.nl>
 * @package View_Admin_As
 */

if ( ! defined( 'VIEW_ADMIN_AS_DIR' ) ) {
    die();
}

/**
 * Compatibility class for the Groups plugin
 *
 * Tested from Groups version: 2.1.2
 *
 * @author  Jory Hogeveen <info@keraweb.nl>
 * @package View_Admin_As
 * @since   1.7.2
 * @version 1.8.7
 * @uses    \VAA_View_Admin_As_Type Extends class
 */
final class VAA_View_Admin_As_Groups extends VAA_View_Admin_As_Type
{
    /**
     * The single instance of the class.
     *
     * @since  1.7.2
     * @static
     * @var    \VAA_View_Admin_As_Groups
     */
    private static $_instance = null;

    /**
     * @since  1.7.2
     * @since  1.8.0  Renamed from `$viewKey`.
     * @var    string
     */
    protected $type = 'groups';

    /**
     * The view icon.
     *
     * @todo Check for `dashicons-itthinx-groups`: https://github.com/itthinx/groups/pull/61
     *
     * @since  1.7.6
     * @var    string
     */
    protected $icon = 'dashicons-image-filter';

    /**
     * @since  1.7.2
     * @since  1.8.0  Renamed from `$selectedGroup`.
     * @see    \Groups_Group >> groups/lib/core/class-groups-group.php
     * @var    \Groups_Group
     */
    protected $selected;

    /**
     * @since  1.7.4
     * @var    string
     */
    protected $groupsScreen = 'groups-admin';

    /**
     * Populate the instance and validate Groups plugin.
     *
     * @since   1.7.2
     * @access  protected
     * @param   \VAA_View_Admin_As  $vaa  The main VAA object.
     */
    protected function __construct( $vaa ) {
        self::$_instance = $this;

        if ( is_network_admin() || ! VAA_API::exists_callable( array( 'Groups_Group', 'get_groups' ), 'debug' ) ) {
            return;
        }

        $this->cap = ( defined( 'GROUPS_ADMINISTER_GROUPS' ) ) ? GROUPS_ADMINISTER_GROUPS : 'manage_options';

        parent::__construct( $vaa );

        if ( ! $this->has_access() ) {
            return;
        }

        $this->priorities['toolbar'] = 40;

        $this->label          = $this->translate_remote( 'Groups' );
        $this->label_singular = $this->translate_remote( 'Group' );
        $this->description    = __( 'Plugin' ) . ': ' . $this->label;

        // Add groups capabilities.
        $this->capabilities[] = $this->cap;
        if ( defined( 'GROUPS_ACCESS_GROUPS' ) ) {
            $this->capabilities[] = GROUPS_ACCESS_GROUPS;
        }
        if ( defined( 'GROUPS_ADMINISTER_OPTIONS' ) ) {
            $this->capabilities[] = GROUPS_ADMINISTER_OPTIONS;
        }
        if ( defined( 'GROUPS_RESTRICT_ACCESS' ) ) {
            $this->capabilities[] = GROUPS_RESTRICT_ACCESS;
        }
        // Do not add to VAA capabilities.
        $this->add_filter( 'members_get_capabilities', array( $this, 'add_capabilities' ) );
    }

    /**
     * Setup module and hooks.
     *
     * @since   1.7.4
     * @access  private
     */
    public function init() {

        if ( parent::init() ) {

            if ( defined( 'GROUPS_PLUGIN_URL' ) ) {
                $this->icon = GROUPS_PLUGIN_URL . '/images/groups.png';
            }
        } else {
            // Add this anyway.
            $this->add_action( 'vaa_view_admin_as_do_view', array( $this, 'do_view' ) );
        }
    }

    /**
     * Initialize the Groups module.
     * @since   1.7.2
     * @access  public
     */
    public function do_view() {

        if ( parent::do_view() ) {

            $this->selected = new Groups_Group( $this->store->get_view( $this->type ) );

            $this->reset_groups_user();

            $this->add_action( 'vaa_view_admin_as_modify_user', array( $this, 'modify_user' ), 10, 2 );
            $this->init_user_modifications();

            $this->add_filter( 'groups_post_access_user_can_read_post', array( $this, 'groups_post_access_user_can_read_post' ), 99, 3 );

            /**
             * Replicate 404 page when the selected user has no access to read.
             * I use this since I can't hook into the `posts_where` filter from Groups.
             * @see VAA_View_Admin_As_Groups::groups_post_access_user_can_read_post()
             */
            $this->add_action( 'wp', array( $this, 'post_access_404' ) );
            //$this->add_filter( 'groups_post_access_posts_where_apply', '__return_false' );

            remove_shortcode( 'groups_member' );
            remove_shortcode( 'groups_non_member' );
            add_shortcode( 'groups_member', array( $this, 'shortcode_groups_member' ) );
            add_shortcode( 'groups_non_member', array( $this, 'shortcode_groups_non_member' ) );

            // Filter user-group relationships.
            //$this->add_filter( 'groups_user_is_member', array( $this, 'groups_user_is_member' ), 20, 3 );
        }

        // Filter group capabilities.
        if ( VAA_API::is_user_modified() ) {
            $this->add_filter( 'groups_group_can', array( $this, 'groups_group_can' ), 20, 3 );
            $this->add_filter( 'groups_user_can', array( $this, 'groups_user_can' ), 20, 3 );
        }
    }

    /**
     * Reset Groups_User data for the selected user.
     *
     * @see  \Groups_Cache
     * @see  \Groups_User
     *
     * @since   1.7.2
     * @access  public
     * @param   int  $user_id
     */
    public function reset_groups_user( $user_id = null ) {
        if ( ! VAA_API::exists_callable( array( 'Groups_User', 'clear_cache' ), 'debug' ) ) {
            return;
        }

        if ( ! $user_id ) {
            $user_id = $this->store->get_selectedUser()->ID;
        }

        try {

            Groups_User::clear_cache( $user_id );

            $capabilities_base   = array();
            $capability_ids_base = array();
            $groups_ids_base     = array( $this->selected->group_id );
            $groups_base         = array( $this->selected );
            $capabilities        = null;
            $capability_ids      = null;
            $groups_ids          = null;
            $groups              = null;

            Groups_Cache::set( Groups_User::CAPABILITIES_BASE . $user_id, $capabilities_base, Groups_User::CACHE_GROUP );
            Groups_Cache::set( Groups_User::CAPABILITY_IDS_BASE . $user_id, $capability_ids_base, Groups_User::CACHE_GROUP );
            Groups_Cache::set( Groups_User::GROUP_IDS_BASE . $user_id, $groups_ids_base, Groups_User::CACHE_GROUP );
            Groups_Cache::set( Groups_User::GROUPS_BASE . $user_id, $groups_base, Groups_User::CACHE_GROUP );
            //Groups_Cache::set( Groups_User::CAPABILITIES . $user_id, $capabilities, Groups_User::CACHE_GROUP );
            //Groups_Cache::set( Groups_User::CAPABILITY_IDS . $user_id, $capability_ids, Groups_User::CACHE_GROUP );
            //Groups_Cache::set( Groups_User::GROUP_IDS . $user_id, $groups_ids, Groups_User::CACHE_GROUP );
            //Groups_Cache::set( Groups_User::GROUPS . $user_id, $groups, Groups_User::CACHE_GROUP );

        } catch ( Exception $e ) {

            $this->vaa->add_error_notice( __METHOD__, $e->getMessage() );

        } // End try().
    }

    /**
     * Update the current user's WP_User instance with the current view data.
     *
     * @since   1.7.2
     * @access  public
     * @param   \WP_User  $user        User object.
     */
    public function modify_user( $user ) {

        $caps = array();
        if ( $this->selected ) {

            // Merge the caps with the current selected caps, overwrite existing.
            $group_caps = (array) $this->selected->capabilities_deep;
            foreach ( $group_caps as $group_cap ) {
                /**
                 * @see    \Groups_Capability::create()
                 * @see    \Groups_Capability::__get()
                 * @param  int     $capability_id
                 * @param  string  $capability
                 * @param  string  $class
                 * @param  string  $object
                 * @param  string  $name
                 * @param  string  $description
                 * @param  array   $group_ids
                 */
                if ( isset( $group_cap->capability ) && is_string( $group_cap->capability ) ) {
                    $caps[ $group_cap->capability ] = 1;
                } elseif ( isset( $group_cap->capability->capability ) ) {
                    $caps[ $group_cap->capability->capability ] = 1;
                }
            }
        }

        $caps = array_merge( $this->store->get_selectedCaps(), $caps );

        $this->store->set_selectedCaps( $caps );

        // Merge the caps with the current user caps, overwrite existing.
        $user->allcaps = array_merge( $user->caps, $caps );
    }

    /**
     * Filter the user-group relation.
     *
     * @todo https://github.com/itthinx/groups/pull/59
     * @see  \Groups_User_Group::read() >> groups/lib/core/class-groups-user-group.php
     *
     * @since   1.7.2
     * @access  public
     * @param   bool  $result    Current result.
     * @param   int   $user_id   User ID.
     * @param   int   $group_id  Group ID.
     * @return  bool|object
     */
    public function groups_user_is_member( $result, $user_id, $group_id ) {
        if (
            (int) $user_id === (int) $this->store->get_curUser()->ID
            && $this->selected
            && (int) $group_id === (int) $this->selected->group->group_id
        ) {
            $result = $this->selected->group;
        }
        return $result;
    }

    /**
     * Filter for the current view.
     *
     * @see  \Groups_User::can() >> groups/lib/core/class-groups-user.php
     *
     * @since   1.7.2
     * @access  public
     * @param   bool           $result  Current result.
     * @param   \Groups_Group  $object  (not used) Group object.
     * @param   string         $cap     Capability.
     * @return  bool
     */
    public function groups_user_can( $result, $object = null, $cap = '' ) {

        /**
         * Fallback PHP < 5.4 due to apply_filters_ref_array
         * @see https://codex.wordpress.org/Function_Reference/apply_filters_ref_array
         */
        if ( is_array( $result ) ) {
            $cap = $result[2];
            //$object = $result[1];
            $result = $result[0];
        }

        if ( ! $this->store->get_view() ) {
            return $result;
        }

        if (
            $this->selected
            && is_callable( array( $this->selected, 'can' ) )
            && ! $this->selected->can( $cap )
        ) {
            $result = false;
        } else {
            // For other view types.
            $result = VAA_API::current_view_can( $cap );
        }
        return $result;
    }

    /**
     * Filter for the current view.
     *
     * @see  \Groups_Group::can() >> groups/lib/core/class-groups-group.php
     *
     * @since   1.7.2
     * @access  public
     * @param   bool           $result  Current result.
     * @param   \Groups_Group  $object  Group object.
     * @param   string         $cap     Capability.
     * @return  bool
     */
    public function groups_group_can( $result, $object = null, $cap = '' ) {
        // Prevent loop on `groups_user_can` filter.
        if ( $this->selected && $this->selected->group_id === $object->group_id ) {
            return $result;
        }
        return $this->groups_user_can( $result, $object, $cap );
    }

    /**
     * Filter whether the user can do something with a post.
     *
     * @see  \Groups_Post_Access::user_can_read_post()
     *
     * @since   1.7.2
     * @access  public
     * @param   bool  $result
     * @param   int   $post_id
     * @param   int   $user_id
     * @return  bool
     */
    public function groups_post_access_user_can_read_post( $result, $post_id, $user_id ) {
        if ( $this->store->get_selectedUser()->ID !== $user_id || ! $this->selected ) {
            return $result;
        }
        if ( ! VAA_API::exists_callable( array( 'Groups_Post_Access', 'get_read_group_ids' ), 'debug' ) ) {
            return $result;
        }

        $post_access = Groups_Post_Access::get_read_group_ids( $post_id );

        $result = true;
        if ( ! empty( $post_access ) && ! in_array( $this->selected->group_id, $post_access, true ) ) {
            $result = false;
        }
        return $result;
    }

    /**
     * Replicate 404 page when the selected user has no access to read.
     * I use this since I can't hook into the `posts_where` filter from Groups.
     *
     * @hook    `wp`
     * @see     \VAA_View_Admin_As_Groups::groups_post_access_user_can_read_post()
     *
     * @since   1.7.2
     * @access  public
     */
    public function post_access_404() {
        global $post;
        if ( isset( $post->ID ) && ! $this->groups_post_access_user_can_read_post( true, $post->ID, $this->store->get_selectedUser()->ID ) ) {
            global $wp_query;
            $wp_query->set_404();
        }
    }

    /**
     * Our own implementation for the groups_member shortcode.
     *
     * @see  \Groups_Access_Shortcodes::groups_member()
     *
     * @since   1.7.2
     * @param   array   $atts
     * @param   string  $content
     * @return  string
     */
    public function shortcode_groups_member( $atts, $content ) {
        return $this->shortcode_member( $atts, $content, false );
    }

    /**
     * Our own implementation for the groups_non_member shortcode.
     *
     * @see  \Groups_Access_Shortcodes::groups_non_member()
     *
     * @since   1.7.2
     * @param   array   $atts
     * @param   string  $content
     * @return  string
     */
    public function shortcode_groups_non_member( $atts, $content ) {
        return ! $this->shortcode_member( $atts, $content, true );
    }

    /**
     * Our own implementation for the Groups member shortcodes.
     *
     * @see  \VAA_View_Admin_As_Groups::shortcode_groups_member()
     * @see  \VAA_View_Admin_As_Groups::shortcode_groups_non_member()
     *
     * @since   1.7.2
     * @param   array   $atts
     * @param   string  $content
     * @param   bool    $reverse
     * @return  string
     */
    public function shortcode_member( $atts, $content, $reverse = false ) {
        $output       = '';
        $shortcode    = ( $reverse ) ? 'groups_non_member' : 'groups_member';
        $options      = shortcode_atts( array( 'group' => '' ), $atts ); //, $shortcode
        $show_content = false;
        if ( null !== $content ) {
            $groups = explode( ',', $options['group'] );
            foreach ( $groups as $group ) {
                $group          = trim( $group );
                $selected_group = $this->selected;
                $current_group  = Groups_Group::read( $group );
                if ( ! $current_group ) {
                    $current_group = Groups_Group::read_by_name( $group );
                }
                if ( $current_group && $current_group->group_id === $selected_group->group_id ) {
                    $show_content = ! $reverse;
                    break;
                }
            }
            if ( $show_content ) {
                remove_shortcode( $shortcode );
                $content = VAA_API::apply_shortcodes( $content );
                add_shortcode( $shortcode, array( $this, 'shortcode_' . $shortcode ) );
                $output = $content;
            }
        }
        return $output;
    }

    /**
     * Validate data for this view type
     *
     * @since   1.7.2
     * @param   null   $null  Default return (invalid)
     * @param   mixed  $data  The view data
     * @return  mixed
     */
    public function validate_view_data( $null, $data = null ) {
        if ( is_numeric( $data ) && $this->get_groups( (int) $data ) ) {
            return (int) $data;
        }
        return $null;
    }

    /**
     * Get the view title.
     *
     * @since   1.8.7
     * @param   string  $key  The data key.
     * @return  string
     */
    public function get_view_title( $key ) {
        $title = $key;
        $item  = $this->get_groups( $key );
        if ( $item ) {
            $title = $item->name;
        }

        /**
         * Change the display title for view type nodes.
         *
         * @since  1.8.0
         * @param  string  $title  Group title.
         * @param  string  $key    Group key.
         * @return string
         */
        $title = apply_filters( 'vaa_admin_bar_view_title_' . $this->type, $title, $key );

        return $title;
    }

    /**
     * Add the Groups admin bar items.
     *
     * @since   1.7.2
     * @access  public
     * @param   \WP_Admin_Bar  $admin_bar  The toolbar object.
     * @param   string         $root       The root item.
     */
    public function admin_bar_menu( $admin_bar, $root ) {

        if ( ! $this->get_groups() || ! count( $this->get_groups() ) ) {
            return;
        }

        $admin_bar->add_group( array(
            'id'     => $root . '-groups',
            'parent' => $root,
            'meta'   => array(
                'class' => 'ab-sub-secondary',
            ),
        ) );

        $root = $root . '-groups';

        $admin_bar->add_node( array(
            'id'     => $root . '-title',
            'parent' => $root,
            'title'  => VAA_View_Admin_As_Form::do_icon( $this->icon ) . $this->label,
            'href'   => false,
            'meta'   => array(
                'class'    => 'vaa-has-icon ab-vaa-title ab-vaa-toggle active',
                'tabindex' => '0',
            ),
        ) );

        $admin_bar->add_node( array(
            'id'     => $root . '-admin',
            'parent' => $root,
            'title'  => VAA_View_Admin_As_Form::do_description(
                VAA_View_Admin_As_Form::do_icon( 'dashicons-admin-links' )
                . __( 'Plugin' ) . ': ' . $this->label
            ),
            'href'   => menu_page_url( $this->groupsScreen, false ),
            'meta'   => array(
                'class' => 'auto-height',
            ),
        ) );

        /**
         * Add items at the beginning of the groups group.
         *
         * @see     'admin_bar_menu' action
         * @link    https://codex.wordpress.org/Class_Reference/WP_Admin_Bar
         * @param   \WP_Admin_Bar  $admin_bar   The toolbar object.
         * @param   string         $root        The current root item.
         */
        $this->do_action( 'vaa_admin_bar_groups_before', $admin_bar, $root );

        // Add the groups.
        foreach ( $this->get_groups() as $group_key => $group ) {
            $view_value = $group->group_id;
            $view_data  = array( $this->type => $view_value );
            $href       = VAA_API::get_vaa_action_link( $view_data );
            $class      = 'vaa-' . $this->type . '-item';
            $title      = VAA_View_Admin_As_Form::do_view_title( $group->name, $this, $view_value );
            // Check if this group is the current view.
            if ( $this->store->get_view( $this->type ) ) {
                if ( (int) $this->store->get_view( $this->type ) === (int) $group->group_id ) {
                    $class .= ' current';
                    $href   = false;
                } else {
                    $selected = $this->get_groups( $this->store->get_view( $this->type ) );
                    if ( (int) $selected->parent_id === (int) $group->group_id ) {
                        $class .= ' current-parent';
                    }
                }
            }
            $parent = $root;
            if ( ! empty( $group->parent_id ) ) {
                $parent = $root . '-' . $this->type . '-' . (int) $group->parent_id;
            }
            $admin_bar->add_node( array(
                'id'     => $root . '-' . $this->type . '-' . (int) $group->group_id,
                'parent' => $parent,
                'title'  => $title,
                'href'   => $href,
                'meta'   => array(
                    // Translators: %s stands for the view type name.
                    'title' => sprintf( __( 'View as %s', VIEW_ADMIN_AS_DOMAIN ), $group->name ),
                    'class' => $class,
                ),
            ) );
        }

        /**
         * Add items at the end of the groups group.
         *
         * @see     'admin_bar_menu' action
         * @link    https://codex.wordpress.org/Class_Reference/WP_Admin_Bar
         * @param   \WP_Admin_Bar  $admin_bar   The toolbar object.
         * @param   string         $root        The current root item.
         */
        $this->do_action( 'vaa_admin_bar_groups_after', $admin_bar, $root );
    }

    /**
     * Store the available groups.
     * @since   1.7.2
     * @since   1.8.0  Renamed from `store_groups()`.
     * @access  private
     */
    public function store_data() {
        $groups = Groups_Group::get_groups();

        $data = array();
        if ( ! empty( $groups ) ) {
            foreach ( $groups as $group ) {
                $data[ $group->group_id ] = $group;
            }
        }
        $this->set_data( $data );
    }

    /**
     * Get a group by ID.
     *
     * @since   1.7.2
     * @access  public
     * @param   string  $key  The group key.
     * @return  \Groups_Group[]|\Groups_Group|bool
     */
    public function get_groups( $key = '-1' ) {
        if ( ! is_numeric( $key ) ) {
            return false;
        }
        if ( '-1' === $key ) {
            $key = null;
        }
        return $this->get_data( $key );
    }

    /**
     * Translate with another domain.
     *
     * @since   1.7.2
     * @param   string  $string  The string.
     * @return  string
     */
    public function translate_remote( $string ) {
        $domain = ( defined( 'GROUPS_PLUGIN_DOMAIN' ) ) ? GROUPS_PLUGIN_DOMAIN : 'groups';
        // @codingStandardsIgnoreLine >> Prevent groups translation from getting parsed by translate.wordpress.org
        return __( $string, $domain );
    }

    /**
     * Main Instance.
     *
     * Ensures only one instance of this class is loaded or can be loaded.
     *
     * @since   1.7.2
     * @access  public
     * @static
     * @param   \VAA_View_Admin_As  $caller  The referrer class.
     * @return  \VAA_View_Admin_As_Groups  $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_Groups.