/home/arranoyd/telegastro/wp-content/plugins/restrict-user-access/level.php
<?php
/**
 * @package Restrict User Access
 * @author Joachim Jensen <joachim@dev.institute>
 * @license GPLv3
 * @copyright 2020 by Joachim Jensen
 */

defined('ABSPATH') || exit;

final class RUA_Level_Manager
{

    /**
     * Metadata
     *
     * @var WPCAObjectManager
     */
    private $metadata;

    public function __construct()
    {
        $this->add_actions();
        $this->add_filters();

        add_shortcode('restrict', array($this,'shortcode_restrict'));
        add_shortcode('restrict-inner', array($this,'shortcode_restrict'));
    }

    /**
     * Add callbacks to actions queue
     *
     * @since 0.5
     */
    protected function add_actions()
    {
        add_action(
            'template_redirect',
            array($this,'authorize_access')
        );
        add_action(
            'init',
            array($this,'create_restrict_type'),
            99
        );
        add_action(
            'user_register',
            array($this,'registered_add_level')
        );
    }

    /**
     * Add callbacks to filters queue
     *
     * @since 0.5
     */
    protected function add_filters()
    {
        if (!is_admin()) {
            add_filter(
                'show_admin_bar',
                array($this,'show_admin_toolbar'),
                99
            );
        }

        //hook early, other plugins might add dynamic caps later
        //fixes problem with WooCommerce Orders
        add_filter(
            'user_has_cap',
            array($this,'user_level_has_cap'),
            9,
            4
        );
    }

    /**
     * Maybe hide admin toolbar for Users
     *
     * @since  1.1
     * @return bool
     */
    public function show_admin_toolbar($show)
    {
        $user = rua_get_user();

        if ($user->has_global_access()) {
            return $show;
        }

        $levels = $user->get_level_ids();

        if (empty($levels)) {
            return $show;
        }

        $metadata = $this->metadata()->get('hide_admin_bar');

        //if user has at least 1 level without this option
        //don't hide the toolbar
        foreach ($levels as $level_id) {
            if ($metadata->get_data($level_id) != '1') {
                return $show;
            }
        }

        return false;
    }


    /**
     * Get level by name
     *
     * @since  0.6
     * @param  string  $name
     * @return WP_Post|bool
     */
    public function get_level_by_name($name)
    {
        $all_levels = RUA_App::instance()->get_levels();
        foreach ($all_levels as $id => $level) {
            if ($level->post_name == $name && $level->post_status == RUA_App::STATUS_ACTIVE) {
                return $level;
            }
        }
        return false;
    }

    /**
     * Restrict content in shortcode
     *
     * @version 0.1
     * @param   array     $atts
     * @param   string    $content
     * @return  string
     */
    public function shortcode_restrict($atts, $content = null)
    {
        $user = rua_get_user();
        if ($user->has_global_access()) {
            return do_shortcode($content);
        }

        $a = shortcode_atts(array(
            'role'      => '',
            'level'     => '',
            'page'      => 0,
            'drip_days' => 0
        ), $atts, 'restrict');

        $has_access = false;

        if ($a['level'] !== '') {
            $user_levels = array_flip($user->get_level_ids());
            if (!empty($user_levels)) {
                $level_names = explode(',', str_replace(' ', '', $a['level']));
                $not_found = 0;
                foreach ($level_names as $level_name) {
                    $level = $this->get_level_by_name(ltrim($level_name, '!'));
                    if (!$level) {
                        $not_found++;
                        continue;
                    }
                    //if level param is negated, give access only if user does not have it
                    if ($level->post_name != $level_name) {
                        $has_access = !isset($user_levels[$level->ID]);
                    } elseif (isset($user_levels[$level->ID])) {
                        $drip = (int)$a['drip_days'];
                        if ($drip > 0 && $this->metadata()->get('role')->get_data($level->ID) === '') {
                            $start = $user->get_level_start($level->ID);
                            $drip_time = strtotime('+'.$drip.' days 00:00', $start);
                            $should_drip = apply_filters(
                                'rua/auth/content-drip',
                                time() <= $drip_time,
                                $user,
                                $level->ID
                            );
                            if ($should_drip) {
                                continue;
                            }
                        }
                        $has_access = true;
                    }
                    if ($has_access) {
                        break;
                    }
                }
                //if levels do not exist, make content visible
                if (!$has_access && $not_found && $not_found === count($level_names)) {
                    $has_access = true;
                }
            }
        } elseif ($a['role'] !== '') {
            $user_roles = array_flip(wp_get_current_user()->roles);
            if (!empty($user_roles)) {
                $roles = explode(',', str_replace(' ', '', $a['role']));
                foreach ($roles as $role_name) {
                    $role = ltrim($role_name, '!');
                    $not = $role != $role_name;
                    //when role is negated, give access if user does not have it
                    //otherwise give access only if user has it
                    if ($not xor isset($user_roles[$role])) {
                        $has_access = true;
                        break;
                    }
                }
            }
        }

        /**
         * @var bool $has_access
         * @var RUA_User_Interface $user
         * @var array $a
         */
        $has_access = apply_filters('rua/shortcode/restrict', $has_access, $user, $a);

        if (!$has_access) {
            $content = '';

            // Only apply the page content if it exists
            $page = $a['page'] ? get_post($a['page']) : null;
            if ($page) {
                setup_postdata($page);
                $content = get_the_content();
                wp_reset_postdata();
            }
        }

        return do_shortcode($content);
    }

    /**
     * Get instance of metadata manager
     *
     * @since  1.0
     * @return WPCAObjectManager
     */
    public function metadata()
    {
        if (!$this->metadata) {
            $this->_init_metadata();
        }
        return $this->metadata;
    }

    /**
     * Create and populate metadata fields
     *
     * @since  0.1
     * @return void
     */
    private function _init_metadata()
    {
        $this->metadata = new WPCAObjectManager();
        $this->metadata
        ->add(new WPCAMeta(
            'role',
            __('Synchronized Role'),
            '',
            'select',
            array()
        ), 'role')
        ->add(new WPCAMeta(
            'handle',
            _x('Non-Member Action', 'option', 'restrict-user-access'),
            0,
            'select',
            array(
                0 => __('Redirect', 'restrict-user-access'),
                1 => __('Tease & Include', 'restrict-user-access')
            ),
            __('Redirect to another page or show teaser.', 'restrict-user-access')
        ), 'handle')
        ->add(new WPCAMeta(
            'page',
            __('Page'),
            0,
            'select',
            array(),
            __('Page to redirect to or display content from under teaser.', 'restrict-user-access')
        ), 'page')
        ->add(new WPCAMeta(
            'duration',
            __('Duration'),
            'day',
            'select',
            array(
                'day'   => __('Day(s)', 'restrict-user-access'),
                'week'  => __('Week(s)', 'restrict-user-access'),
                'month' => __('Month(s)', 'restrict-user-access'),
                'year'  => __('Year(s)', 'restrict-user-access')
            ),
            __('Set to 0 for unlimited.', 'restrict-user-access')
        ), 'duration')
        ->add(new WPCAMeta(
            'caps',
            __('Capabilities'),
            array(),
            '',
            array(),
            '',
            array($this,'sanitize_capabilities')
        ), 'caps')
        ->add(new WPCAMeta(
            'hide_admin_bar',
            __('Hide Admin Toolbar'),
            '',
            'checkbox',
            array(),
            ''
        ), 'hide_admin_bar')
        ->add(new WPCAMeta(
            'default_access',
            __('Default Access'),
            1,
            'select',
            array(
                1 => 'All unrestricted content',
                0 => 'Restricted content only'
            ),
            ''
        ), 'default_access');

        apply_filters('rua/metadata', $this->metadata);
    }

    /**
     * @param array|mixed $value
     *
     * @return array
     */
    public function sanitize_capabilities($value)
    {
        if (is_array($value)) {
            $inherited_caps = isset($_POST['inherited_caps']) ? $_POST['inherited_caps'] : array();
            foreach ($value as $name => $cap) {
                /**
                 * do not save if:
                 * - value is equal to inherited
                 * - no inherited value and unsetting
                 */
                if (isset($inherited_caps[$name]) ? $inherited_caps[$name] == $cap
                    : $cap == -1) {
                    unset($value[$name]);
                }
            }
        }
        return $value;
    }

    /**
     * Populate input fields for metadata
     *
     * @since  0.8
     * @return void
     */
    public function populate_metadata()
    {
        $role_list = array(
            '' => __('-- None --', 'restrict-user-access'),
            -1 => __('Logged-in', 'restrict-user-access'),
            0  => __('Not logged-in', 'restrict-user-access')
        );

        foreach (get_editable_roles() as $id => $role) {
            $role_list[$id] = $role['name'];
        }

        $this->metadata()->get('role')->set_input_list($role_list);
    }

    /**
     * Create restrict post type and add it to WPCACore
     *
     * @since  0.1
     * @return void
     */
    public function create_restrict_type()
    {

        // Register the sidebar type
        register_post_type(RUA_App::TYPE_RESTRICT, array(
            'labels' => array(
                'name'               => __('Access Levels', 'restrict-user-access'),
                'singular_name'      => __('Access Level', 'restrict-user-access'),
                'add_new'            => _x('Add New', 'level', 'restrict-user-access'),
                'add_new_item'       => __('Add New Access Level', 'restrict-user-access'),
                'edit_item'          => __('Edit Access Level', 'restrict-user-access'),
                'new_item'           => __('New Access Level', 'restrict-user-access'),
                'all_items'          => __('Access Levels', 'restrict-user-access'),
                'view_item'          => __('View Access Level', 'restrict-user-access'),
                'search_items'       => __('Search Access Levels', 'restrict-user-access'),
                'not_found'          => __('No Access Levels found', 'restrict-user-access'),
                'not_found_in_trash' => __('No Access Levels found in Trash', 'restrict-user-access'),
                'parent_item_colon'  => __('Extend Level', 'restrict-user-access'),
                //wp-content-aware-engine specific
                'ca_title' => __('Members-Only Access', 'content-aware-sidebars')
            ),
            'capabilities' => array(
                'edit_post'          => RUA_App::CAPABILITY,
                'read_post'          => RUA_App::CAPABILITY,
                'delete_post'        => RUA_App::CAPABILITY,
                'edit_posts'         => RUA_App::CAPABILITY,
                'delete_posts'       => RUA_App::CAPABILITY,
                'edit_others_posts'  => RUA_App::CAPABILITY,
                'publish_posts'      => RUA_App::CAPABILITY,
                'read_private_posts' => RUA_App::CAPABILITY
            ),
            'public'              => false,
            'hierarchical'        => true,
            'exclude_from_search' => true,
            'publicly_queryable'  => false,
            'show_ui'             => false,
            'show_in_menu'        => false,
            'show_in_nav_menus'   => false,
            'show_in_admin_bar'   => false,
            'menu_icon'           => RUA_App::ICON_SVG,
            'has_archive'         => false,
            'rewrite'             => false,
            'query_var'           => false,
            'supports'            => array('title','page-attributes'),
            'can_export'          => false,
            'delete_with_user'    => false
        ));

        WPCACore::types()->add(RUA_App::TYPE_RESTRICT);
    }

    /**
     * Get conditional restrictions
     * and authorize access for user
     *
     * @since  0.1
     * @return void
     */
    public function authorize_access()
    {
        $rua_user = rua_get_user();

        if ($rua_user->has_global_access()) {
            return;
        }

        $user_levels = array_flip(array_reverse($rua_user->get_level_ids()));
        $authorized_levels = WPCACore::get_posts(RUA_App::TYPE_RESTRICT);
        $kick = false;

        //does user have level to view unrestricted content by default?
        foreach ($user_levels as $level => $val) {
            if ($this->metadata()->get('default_access')->get_data($level, true)) {
                $kick = false;
                break;
            }
            $kick = $level;
        }

        //does user have authorized level?
        foreach ($authorized_levels as $level) {
            if (isset($user_levels[$level->ID])) {
                $kick = false;
                break;
            }
            $kick = $level->ID;
        }

        if (!empty($authorized_levels) && $kick === false && is_user_logged_in()) {
            $conditions = WPCACore::get_conditions(RUA_App::TYPE_RESTRICT);
            foreach ($conditions as $condition => $level) {
                //Check post type
                if (!isset($authorized_levels[$level])) {
                    continue;
                }

                $drip = get_post_meta($condition, RUA_App::META_PREFIX.'opt_drip', true);
                //Restrict access to dripped content
                if ($drip && $this->metadata()->get('role')->get_data($level) === '') {
                    $start = $rua_user->get_level_start($level);
                    $drip_time = strtotime('+'.$drip.' days 00:00', $start);
                    $should_drip = apply_filters(
                        'rua/auth/content-drip',
                        time() <= $drip_time,
                        $rua_user,
                        $level
                    );
                    if ($should_drip) {
                        $kick = $level;
                        continue;
                    }
                }
                $kick = false;
                break;
            }
        }

        if ($kick === false) {
            return;
        }

        $action = $this->metadata()->get('handle')->get_data($kick);
        self::$page = $this->metadata()->get('page')->get_data($kick);
        switch ($action) {
            case 0:
                $redirect = '';

                $current_path = remove_query_arg('redirect_to', add_query_arg(null, null));
                $parts = parse_url(get_site_url());
                $pos = isset($parts['path']) ? stripos($current_path, $parts['path']) : false;
                if ($pos !== false) {
                    $relative_path = substr($current_path, $pos + strlen($parts['path']));
                } else {
                    $relative_path = $current_path;
                }

                if (is_numeric(self::$page)) {
                    if (self::$page != get_the_ID()) {
                        $redirect = get_permalink(self::$page);
                    }
                } else {
                    /**
                     * WP always appends /
                     * also check case where non-member action does not have it,
                     * which can cause infinite loop
                     */
                    if ($relative_path != self::$page && $relative_path != self::$page.'/') {
                        $redirect = get_site_url().self::$page;
                    }
                }

                //only redirect if current page != redirect page
                if ($redirect) {
                    wp_safe_redirect(add_query_arg(
                        'redirect_to',
                        urlencode($current_path),
                        $redirect
                    ));
                    exit;
                }
                break;
            case 1:
                add_filter('the_content', array($this,'content_tease'), 8);
                break;
            default: break;
        }
    }

    /**
     * Carry over page from restriction metadata
     * @var integer
     */
    public static $page = false;

    /**
     * Limit content to only show teaser and
     * page content from restriction metadata
     *
     * @since   0.1
     * @param   string    $content
     * @return  string
     */
    public function content_tease($content)
    {
        if (preg_match('/(<span id="more-[0-9]*"><\/span>)/', $content, $matches)) {
            $teaser = explode($matches[0], $content, 2);
            $content = $teaser[0];
        } else {
            $content = '';
        }

        if (is_numeric(self::$page)) {
            setup_postdata(get_post(self::$page));
            $content .= get_the_content();
            wp_reset_postdata();
        }

        return $content;
    }

    /**
     * Override user caps with level caps.
     *
     * @param  array   $allcaps
     * @param  string  $cap
     * @param  array   $args {
     *     @type string  [0] Requested capability
     *     @type int     [1] User ID
     *     @type WP_User [2] Associated object ID (User object)
     * }
     * @param  WP_User $user
     *
     * @return array
     */
    public function user_level_has_cap($allcaps, $cap, $args, $user)
    {
        $rua_user = rua_get_user($user);

        $global_access = $rua_user->has_global_access();

        // if ($cap && $cap[0] == RUA_App::CAPABILITY && $global_access ) {
        // 	$allcaps[ $cap[0] ] = true;
        // }

        if (!$global_access && defined('WPCA_VERSION')) {
            $allcaps = $rua_user->get_caps($allcaps);
        }
        return $allcaps;
    }

    /**
     * Get all capabilities of one or multiple levels
     *
     * If you pass an array the order of these levels should be set correctly!
     * The first level caps will be overwritten by the second etc.
     *
     * @since  0.13
     * @param  array|int  $levels
     * @return array
     */
    public function get_levels_caps($levels)
    {
        $levels = (array) $levels;
        $caps = array();
        foreach ($levels as $level) {
            $level_caps = $this->metadata()->get('caps')->get_data($level, true);
            foreach ($level_caps as $key => $level_cap) {
                if ($level_cap > -1) {
                    $caps[$key] = (bool)$level_cap;
                } else {
                    unset($caps[$key]);
                }
            }
        }
        return $caps;
    }

    /**
     * Maybe add level on user register
     *
     * @since  0.10
     * @param  int  $user_id
     * @return void
     */
    public function registered_add_level($user_id)
    {
        try {
            $level_id = get_option('rua-registration-level', 0);
            if ($level_id) {
                rua_get_user($user_id)->add_level($level_id);
            }
        } catch (Exception $e) {
        }
    }
}