<?php /*

 Composr
 Copyright (c) Christopher Graham, 2004-2024

 See docs/LICENSE.md for full licensing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  Christopher Graham
 * @package    core_cns
 */

/**
 * Hook class.
 */
class Hook_profiles_tabs_rank
{
    /**
     * Find whether this hook is active.
     *
     * @param  MEMBER $member_id_of The ID of the member who is being viewed
     * @param  MEMBER $member_id_viewing The ID of the member who is doing the viewing
     * @return boolean Whether this hook is active
     */
    public function is_active(int $member_id_of, int $member_id_viewing) : bool
    {
        if (!addon_installed('points')) {
            return false;
        }

        if (get_forum_type() != 'cns') {
            return false;
        }

        if ($member_id_of != $member_id_viewing) { // Not even for staff viewing another member's profile because the rank privileges displayed will match the viewer, not the member
            return false;
        }

        $promotion_groups = $GLOBALS['FORUM_DB']->query_select_value('f_groups', 'COUNT(*)', [], ' AND g_promotion_threshold IS NOT NULL AND g_promotion_threshold>0');
        if ($promotion_groups > 0) {
            return true;
        }

        return false;
    }

    /**
     * Render function for profile tab hooks.
     *
     * @param  MEMBER $member_id_of The ID of the member who is being viewed
     * @param  MEMBER $member_id_viewing The ID of the member who is doing the viewing
     * @param  boolean $leave_to_ajax_if_possible Whether to leave the tab contents null, if this hook supports it, so that AJAX can load it later
     * @return array A tuple: The tab title, the tab contents, the suggested tab order, the icon
     */
    public function render_tab(int $member_id_of, int $member_id_viewing, bool $leave_to_ajax_if_possible = false) : array
    {
        // Initial requirements for cache / AJAX
        require_lang('cns');
        $title = do_lang_tempcode('RANK');
        $order = 70;

        if ($leave_to_ajax_if_possible) {
            return [$title, null, $order, 'menu/social/groups'];
        }

        // Quickly grab all groups involved in ranks
        $rows = $GLOBALS['FORUM_DB']->query_select('f_groups', ['id', 'g_promotion_threshold'], [], ' AND g_promotion_threshold IS NOT NULL');
        $promotion_groups = collapse_2d_complexity('id', 'g_promotion_threshold', $rows);
        sort($promotion_groups, SORT_NUMERIC);

        require_code('points');
        $cache_identifier = serialize([$member_id_of, points_lifetime($member_id_of), $promotion_groups]);

        // Try the cache first because this hook uses quite a bit of resources and database queries
        require_code('caches');
        $content = get_cache_entry('profiles_tabs_rank', $cache_identifier, CACHE_AGAINST_DEFAULT, 60, true);
        if ($content !== null) {
            return [$title, $content, $order, 'menu/social/groups'];
        }

        require_all_lang(null, true); // Necessary for privilege listings

        require_code('cns_general');
        require_code('cns_groups');

        $member_info = cns_read_in_member_profile($member_id_of, [
            'primary_group',
            'secondary_groups',
            'num_points_advance'
        ]);

        // Add the probation group to the bottom of the ladder if one exists
        $groups = [];
        $probation_group = get_probation_group();
        if ($probation_group !== null) {
            $groups[] = [
                'RANK_GROUP' => $probation_group,
                'RANK_NAME' => cns_get_group_name($probation_group),
                'RANK_THRESHOLD' => null,
                'RANK_STATUS' => '0',
            ];
        }

        // Now determine the group at the bottom of the (actual) rank ladder
        $group = get_first_default_group();
        if (!isset($promotion_groups[$group])) { // Default group not part of rank ladder? Try to find the rank with the lowest threshold.
            $group = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_groups', 'id', [], ' AND g_promotion_threshold IS NOT NULL ORDER BY g_promotion_threshold ASC');
        }
        if ($group === null) { // Still nothing? Error because the rank tab should not have been visible.
            warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
        }

        // Populate our rank ladder
        $threshold = 0;
        do {
            $_group = $group;
            $groups[] = [
                'RANK_GROUP' => $_group,
                'RANK_NAME' => cns_get_group_name($_group),
                'RANK_THRESHOLD' => $threshold,
                'RANK_STATUS' => '0',
            ];
            $group = $GLOBALS['FORUM_DB']->query_select_value('f_groups', 'g_promotion_target_group', ['id' => $_group]);
            $threshold = $GLOBALS['FORUM_DB']->query_select_value('f_groups', 'g_promotion_threshold', ['id' => $_group]);
        } while (($group !== null) && ($threshold !== null));
        sort_maps_by($groups, 'RANK_THRESHOLD');

        // Now get the groups which the member is in
        $member_groups = $member_info['secondary_groups'];
        $member_groups[] = $member_info['primary_group'];

        $rank = 0; // Index of $groups indicating which rank the member is on
        $rank_name = '';
        if (isset($member_info['on_probation_until']) && ($groups[0]['RANK_THRESHOLD'] === null)) { // Oops! Probation takes priority
            $groups[0]['RANK_STATUS'] = '1'; // In this rank
            $rank_name = $groups[0]['RANK_NAME'];
        } else {
            // Find out which rank group member is in from highest index to lowest
            $found_rank = false;
            for ($i = (count($groups) - 1); $i >= 0; $i--) {
                if (($found_rank === false) && in_array($groups[$i]['RANK_GROUP'], $member_groups)) {
                    $groups[$i]['RANK_STATUS'] = '1'; // In this rank
                    $rank = $i;
                    $rank_name = $groups[$i]['RANK_NAME'];
                    $found_rank = true;
                } elseif ($found_rank === true) {
                    $groups[$i]['RANK_STATUS'] = '2'; // We passed this rank
                }
            }
        }

        if (empty($rank_name)) {
            return [$title, do_lang_tempcode('RANK_NOT_IN_LADDER'), $order, 'menu/social/groups'];
        }

        // How many points until we advance to the next rank?
        if ((isset($groups[$rank + 1])) && ($groups[$rank + 1]['RANK_THRESHOLD'] !== null)) {
            $num_points_advance = $groups[$rank + 1]['RANK_THRESHOLD'] - points_lifetime($member_id_of);
        } else {
            $num_points_advance = 0;
        }

        // Grab available themes and sort by locked and unlocked
        $locked_themes = [];
        $unlocked_themes = [];
        require_code('themes2');
        $themes = find_all_themes();
        foreach ($themes as $theme => $title) {
            if (get_theme_option('capability_administrative', null, $theme) == '1') { // Do not consider administrative themes
                continue;
            }

            if (($theme == 'default') || has_category_access($member_id_of, 'theme', $theme)) { // Member has access to the theme
                $unlocked_themes[] = $title;
            } elseif ($GLOBALS['SITE_DB']->query_select_value_if_there('group_category_access', 'category_name', [
                'module_the_name' => 'theme',
                'category_name' => strval($theme),
                'group_id' => $groups[count($groups) - 1]['RANK_GROUP'],
            ]) !== null) { // Simple check; the theme should only be listed as locked if the highest ranked group has access to it
                $locked_themes[] = $title;
            }
        }

        // Grab locked and unlocked privileges
        $locked_privileges = [];
        $unlocked_privileges = [];
        $privileges = $GLOBALS['SITE_DB']->query_select('privilege_list', ['the_name', 'the_default'], []);
        foreach ($privileges as $privilege) {
            // exceptions
            if (in_array($privilege, [
                'high_personal_gallery_limit', // We display an explicit number of entries for the gallery on the page
            ])) {
                continue;
            }

            $bonus_privileges = []; // Additional privileges granted in specific areas of the site

            // Check how many ranked groups have this privilege assigned
            $count_assigned = [];
            $count_not_assigned = [];
            for ($i = 0; $i < count($groups); $i++) { // NB: foreach on $groups is against coding standards
                if ($groups[$i]['RANK_THRESHOLD'] === null) { // Ignore probation
                    continue;
                }

                $rows = $GLOBALS['SITE_DB']->query_select('group_privileges', ['*'], ['privilege' => $privilege['the_name'], 'group_id' => $groups[$i]['RANK_GROUP']]);
                foreach ($rows as $row) {
                    $the_value = $privilege['the_default'];
                    if ($row['the_value'] !== null) {
                        $the_value = $row['the_value'];
                    }

                    $bonus_privileges[md5(serialize([$privilege['the_name'], $row['the_page'], $row['module_the_name'], $row['category_name'], $the_value]))] = [ // Prevent duplicates from other groups by assigning an MD5 key
                        'PAGE' => $row['the_page'],
                        'MODULE' => $row['module_the_name'],
                        'CATEGORY' => $row['category_name'],
                        'VALUE' => $the_value,
                    ];

                    if ($the_value == 1) { // Always a ranked privilege if it could ever be assigned based on a rank regardless of scope
                        $count_assigned[$groups[$i]['RANK_GROUP']] = true;
                        unset($count_not_assigned[$groups[$i]['RANK_GROUP']]);
                    } elseif ((!isset($count_assigned[$groups[$i]['RANK_GROUP']])) && (($row['the_page'] . $row['module_the_name'] . $row['category_name']) == '') && ($the_value == 0)) { // Is only not considered assigned if denied globally
                        $count_not_assigned[$groups[$i]['RANK_GROUP']] = true;
                    }
                }
                if (count($rows) == 0) {
                    if ($privilege['the_default'] == 1) {
                        $count_assigned[$groups[$i]['RANK_GROUP']] = true;
                    } else {
                        $count_not_assigned[$groups[$i]['RANK_GROUP']] = true;
                    }
                }
            }

            // If the privilege is assigned to all ranked groups, or it is assigned to none of them, then this is not a ranked-based privilege
            if ((count($count_not_assigned) == count($groups)) || (($groups[0]['RANK_THRESHOLD'] === null) && (count($count_not_assigned) == (count($groups) - 1)))) {
                continue;
            }
            if ((count($count_assigned) == count($groups)) || (($groups[0]['RANK_THRESHOLD'] === null) && (count($count_assigned) == (count($groups) - 1)))) {
                continue;
            }

            foreach ($bonus_privileges as $bonus_privilege) {
                $scope = [];
                if ($bonus_privilege['CATEGORY'] != '') {
                    $scope[] = titleify($bonus_privilege['MODULE']) . ' category ' . $bonus_privilege['CATEGORY'];
                }
                if ($bonus_privilege['PAGE'] != '') {
                    $scope[] = 'page ' . $bonus_privilege['PAGE'];
                }
                if (count($scope) == 0) {
                    if (has_privilege($member_id_of, $privilege['the_name'], '')) { // Blank page because we only want the global result
                        $unlocked_privileges[] = ['PRIVILEGE' => do_lang('PRIVILEGE_' . $privilege['the_name']), 'SCOPE' => 'global'];
                    } else {
                        $locked_privileges[] = ['PRIVILEGE' => do_lang('PRIVILEGE_' . $privilege['the_name']), 'SCOPE' => 'global'];
                    }
                } else {
                    // Over-populates the list
                    /*
                    if (has_privilege($member_id_of, $privilege['the_name'], $bonus_privilege['PAGE'], [$bonus_privilege['MODULE'], $bonus_privilege['CATEGORY']])) {
                        $unlocked_privileges[] = [
                            'PRIVILEGE' => do_lang('PRIVILEGE_' . $privilege['the_name']),
                            'SCOPE' => implode(', ', $scope),
                        ];
                    } else {
                        $locked_privileges[] = [
                            'PRIVILEGE' => do_lang('PRIVILEGE_' . $privilege['the_name']),
                            'SCOPE' => implode(', ', $scope),
                        ];
                    */
                }
            }
        }

        // Get maximum allowed avatar dimensions
        $maximum_avatar_dimensions = strval(cns_get_member_best_group_property($member_id_of, 'max_avatar_width')) . ' x ' . strval(cns_get_member_best_group_property($member_id_of, 'max_avatar_height'));

        // Personal gallery entries
        $personal_galleries_images = null;
        $personal_galleries_videos = null;
        if (addon_installed('galleries')) {
            if (has_privilege($member_id_of, 'high_personal_gallery_limit', '')) {
                $personal_galleries_images = intval(get_option('max_personal_gallery_images_high'));
                $personal_galleries_videos = intval(get_option('max_personal_gallery_videos_high'));
            } else {
                $personal_galleries_images = intval(get_option('max_personal_gallery_images_low'));
                $personal_galleries_videos = intval(get_option('max_personal_gallery_videos_low'));
            }
        }

        // We must now convert RANK_THRESHOLD to string for the template
        for ($i = 0; $i < count($groups); $i++) { // NB: foreach on $groups is against coding standards
            if ($groups[$i]['RANK_THRESHOLD'] === null) {
                $groups[$i]['RANK_THRESHOLD'] = '';
            } else {
                $groups[$i]['RANK_THRESHOLD'] = integer_format($groups[$i]['RANK_THRESHOLD'], 0);
            }
        }

        // Gift points
        if ((addon_installed('points')) && (get_option('enable_gift_points') == '1')) {
            $gift_points = cns_get_member_best_group_property($member_id_of, 'gift_points_base');
            $gift_points_day = cns_get_member_best_group_property($member_id_of, 'gift_points_per_day');
        } else {
            $gift_points = -1;
            $gift_points_day = -1;
        }

        $content = do_template('CNS_MEMBER_PROFILE_RANK', [
            '_GUID' => '23abecddc8a6540709ffc6f318ff6022',
            'SIMPLE_STEPPER' => (count($groups) >= 7) ? '1' : '0', // Use small steppers if we have >=7 groups so it doesn't cramp mobile displays
            'RANKS' => $groups,
            'CURRENT_RANK' => $rank_name,
            '_NUM_POINTS_ADVANCE' => strval($num_points_advance),
            'NUM_POINTS_ADVANCE' => integer_format($num_points_advance),
            'LOCKED_THEMES' => implode(', ', $locked_themes),
            'UNLOCKED_THEMES' => implode(', ', $unlocked_themes),
            'LOCKED_PRIVILEGES' => $locked_privileges,
            'UNLOCKED_PRIVILEGES' => $unlocked_privileges,
            'HAS_DAILY_UPLOAD_QUOTA' => has_privilege($member_id_of, 'exceed_filesize_limit', '') ? '0' : '1',
            'DAILY_UPLOAD_QUOTA' => integer_format(cns_get_member_best_group_property($member_id_of, 'max_daily_upload_mb')),
            '_DAILY_UPLOAD_QUOTA' => strval(cns_get_member_best_group_property($member_id_of, 'max_daily_upload_mb')),
            'MAX_ATTACHMENTS' => integer_format(cns_get_member_best_group_property($member_id_of, 'max_attachments_per_post')),
            '_MAX_ATTACHMENTS' => strval(cns_get_member_best_group_property($member_id_of, 'max_attachments_per_post')),
            'POST_LENGTH' => integer_format(cns_get_member_best_group_property($member_id_of, 'max_post_length_comcode')),
            '_POST_LENGTH' => strval(cns_get_member_best_group_property($member_id_of, 'max_post_length_comcode')),
            'SIGNATURE_LENGTH' => integer_format(cns_get_member_best_group_property($member_id_of, 'max_sig_length_comcode')),
            '_SIGNATURE_LENGTH' => strval(cns_get_member_best_group_property($member_id_of, 'max_sig_length_comcode')),
            'CAN_UPLOAD_AVATARS' => has_privilege($member_id_of, 'own_avatars', '') ? '1' : '0',
            'MAXIMUM_AVATAR_DIMENSIONS' => $maximum_avatar_dimensions,
            'INFINITE_PERSONAL_GALLERY_ENTRIES' => (has_privilege($member_id_of, 'no_personal_gallery_limit', '')) ? '1' : '0',
            'PERSONAL_GALLERY_ENTRIES_IMAGES' => (($personal_galleries_images !== null) ? integer_format($personal_galleries_images) : null),
            '_PERSONAL_GALLERY_ENTRIES_IMAGES' => (($personal_galleries_images !== null) ? strval($personal_galleries_images) : null),
            'PERSONAL_GALLERY_ENTRIES_VIDEOS' => (($personal_galleries_videos !== null) ? integer_format($personal_galleries_videos) : null),
            '_PERSONAL_GALLERY_ENTRIES_VIDEOS' => (($personal_galleries_videos !== null) ? strval($personal_galleries_videos) : null),
            'GIFT_POINTS' => integer_format($gift_points),
            '_GIFT_POINTS' => strval($gift_points),
            'GIFT_POINTS_PER_DAY' => integer_format($gift_points_day),
            '_GIFT_POINTS_PER_DAY' => strval($gift_points_day),
            'FLOOD_CONTROL_SUBMIT' => integer_format(cns_get_member_best_group_property($member_id_of, 'flood_control_submit_secs')),
            '_FLOOD_CONTROL_SUBMIT' => strval(cns_get_member_best_group_property($member_id_of, 'flood_control_submit_secs')),
            'FLOOD_CONTROL_ACCESS' => integer_format(cns_get_member_best_group_property($member_id_of, 'flood_control_access_secs')),
            '_FLOOD_CONTROL_ACCESS' => strval(cns_get_member_best_group_property($member_id_of, 'flood_control_access_secs')),
        ]);

        // Cache the Tempcode
        global $LANGS_REQUESTED, $JAVASCRIPTS, $CSSS;
        require_code('caches2');
        set_cache_entry('profiles_tabs_rank', 60, $cache_identifier, $content, CACHE_AGAINST_DEFAULT, array_keys($LANGS_REQUESTED), array_keys($JAVASCRIPTS), array_keys($CSSS), true);

        return [$title, $content, $order, 'menu/social/groups'];
    }
}
