<?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
 */

/**
 * Module page class.
 */
class Module_members
{
    /**
     * Find details of the module.
     *
     * @return ?array Map of module info (null: module is disabled)
     */
    public function info() : ?array
    {
        $info = [];
        $info['author'] = 'Chris Graham';
        $info['organisation'] = 'Composr';
        $info['hacked_by'] = null;
        $info['hack_version'] = null;
        $info['version'] = 3;
        $info['locked'] = false;
        $info['min_cms_version'] = 11.0;
        $info['addon'] = 'core_cns';
        return $info;
    }

    /**
     * Uninstall the module.
     */
    public function uninstall()
    {
        $privileges = [
            'view_banned_members'
        ];
        delete_privilege($privileges);
    }

    /**
     * Install the module.
     *
     * @param  ?integer $upgrade_from What version we're upgrading from (null: new install)
     * @param  ?integer $upgrade_from_hack What hack version we're upgrading from (null: new-install/not-upgrading-from-a-hacked-version)
     */
    public function install(?int $upgrade_from = null, ?int $upgrade_from_hack = null)
    {
        if (($upgrade_from === null) || ($upgrade_from < 3)) {
            add_privilege('FORUMS_AND_MEMBERS', 'view_banned_members', false);
            $GLOBALS['FORUM_DRIVER']->install_create_custom_field('privacy_last_download', 20, /*locked=*/1, /*viewable=*/0, /*settable=*/0, /*required=*/0, '', 'integer');
            $GLOBALS['FORUM_DRIVER']->install_create_custom_field('privacy_last_purge', 20, /*locked=*/1, /*viewable=*/0, /*settable=*/0, /*required=*/0, '', 'integer');
        }
    }

    /**
     * Find entry-points available within this module.
     *
     * @param  boolean $check_perms Whether to check permissions
     * @param  ?MEMBER $member_id The member to check permissions as (null: current user)
     * @param  boolean $support_crosslinks Whether to allow cross links to other modules (identifiable via a full-page-link rather than a screen-name)
     * @param  boolean $be_deferential Whether to avoid any entry-point (or even return null to disable the page in the Sitemap) if we know another module, or page_group, is going to link to that entry-point. Note that "!" and "browse" entry points are automatically merged with container page nodes (likely called by page-groupings) as appropriate.
     * @return ?array A map of entry points (screen-name=>language-code/string or screen-name=>[language-code/string, icon-theme-image]) (null: disabled)
     */
    public function get_entry_points(bool $check_perms = true, ?int $member_id = null, bool $support_crosslinks = true, bool $be_deferential = false) : ?array
    {
        if (get_forum_type() != 'cns') {
            return null;
        }

        $ret = [
            'browse' => ['MEMBER_DIRECTORY', 'menu/social/members'],
        ];
        if (!$check_perms || !is_guest($member_id)) {
            $ret['view'] = ['MY_PROFILE', 'menu/social/profile'];
        }
        return $ret;
    }

    public $title;
    public $username;
    public $member_id_of;

    /**
     * Module pre-run function. Allows us to know metadata for <head> before we start streaming output.
     *
     * @return ?Tempcode Tempcode indicating some kind of exceptional output (null: none)
     */
    public function pre_run() : ?object
    {
        if (get_forum_type() != 'cns') {
            warn_exit(do_lang_tempcode('NO_CNS'));
        }

        require_code('form_templates'); // Needs to run high so that the anti-click-hacking header is sent

        $type = get_param_string('type', 'browse');

        cns_require_all_forum_stuff();

        require_css('cns');
        require_lang('cns');

        if ($type == 'browse') {
            $this->title = get_screen_title('MEMBER_DIRECTORY');

            require_css('cns_member_directory');
        }

        if ($type == 'view') {
            $username = get_param_string('id', '');
            if (($username == '') || (is_numeric($username))) {
                $member_id_of = get_param_integer('id', null);
                if ($member_id_of === null) {
                    if (is_guest()) {
                        access_denied('NOT_AS_GUEST');
                    }

                    $member_id_of = get_member();
                } elseif (is_guest($member_id_of)) {
                    warn_exit(do_lang_tempcode('MEMBER_NO_EXIST'), false, false, 404);
                }
                $username = $GLOBALS['FORUM_DRIVER']->get_member_row_field($member_id_of, 'm_username');
                if (($username === null) || (is_guest($member_id_of))) {
                    warn_exit(do_lang_tempcode('MEMBER_NO_EXIST'), false, false, 404);
                }
                $is_being_deleted = ($GLOBALS['FORUM_DRIVER']->get_member_row_field($member_id_of, 'm_password_compat_scheme') === 'pending_deletion');
                if ($is_being_deleted) {
                    warn_exit(do_lang_tempcode('MEMBER_NO_EXIST'), false, false, 404);
                }
            } else {
                $member_id_of = $GLOBALS['FORUM_DRIVER']->get_member_from_username($username);
                if ($member_id_of === null) {
                    set_http_status_code(404);
                    warn_exit(do_lang_tempcode('_MEMBER_NO_EXIST', escape_html($username)), false, false, 404);
                }
                if (is_guest($member_id_of)) {
                    warn_exit(do_lang_tempcode('MEMBER_NO_EXIST'), false, false, 404);
                }
                $is_being_deleted = ($GLOBALS['FORUM_DRIVER']->get_member_row_field($member_id_of, 'm_password_compat_scheme') === 'pending_deletion');
                if ($is_being_deleted) {
                    warn_exit(do_lang_tempcode('MEMBER_NO_EXIST'), false, false, 404);
                }
            }

            $member_row = $GLOBALS['FORUM_DRIVER']->get_member_row($member_id_of);

            // Check banned status if we do not have the privilege to view banned members
            if (!has_privilege(get_member(), 'view_banned_members')) {
                $is_perm_banned = $member_row['m_is_perm_banned'];
                if ($is_perm_banned != '0') {
                    warn_exit(do_lang_tempcode('_MEMBER_BANNED'), false, false, 404); // Treat as 404 so indexes remove the member
                }
            }

            set_extra_request_metadata([
                'identifier' => '_SEARCH:members:view:' . strval($member_id_of),
            ], $member_row, 'member', strval($member_id_of));

            breadcrumb_set_parents([['_SELF:_SELF:browse' . propagate_filtercode_page_link(), do_lang_tempcode('MEMBERS')]]);

            if ((get_value('disable_awards_in_titles') !== '1') && (addon_installed('awards'))) {
                require_code('awards');
                $awards = find_awards_for('member', strval($member_id_of));
            } else {
                $awards = [];
            }

            //$this->title = get_screen_title('MEMBER_ACCOUNT', true, [make_fractionable_editable('member', $member_id_of, $username]), null, $awards);
            $displayname = $GLOBALS['FORUM_DRIVER']->get_username($member_id_of, true);
            $username = $GLOBALS['FORUM_DRIVER']->get_username($member_id_of);
            $this->title = get_screen_title('MEMBER_ACCOUNT', true, [escape_html($displayname), escape_html($username)], null, $awards);

            $this->member_id_of = $member_id_of;
            $this->username = $username;

            require_css('cns_member_profiles');
        }

        if ($type == 'unsub') {
            $this->title = get_screen_title('newsletter:NEWSLETTER_UNSUBSCRIBED');
        }

        return null;
    }

    /**
     * Execute the module.
     *
     * @return Tempcode The result of execution
     */
    public function run() : object
    {
        $type = get_param_string('type', 'browse');

        if ($type == 'browse') {
            return $this->directory();
        }
        if ($type == 'view') {
            return $this->profile();
        }
        if ($type == 'unsub') {
            return $this->newsletter_unsubscribe();
        }

        return new Tempcode();
    }

    /**
     * The UI to show the member directory.
     *
     * @return Tempcode The UI
     */
    public function directory() : object
    {
        $tpl = do_template('CNS_MEMBER_DIRECTORY_SCREEN', [
            '_GUID' => '096767e9aaabce9cb3e6591b7bcf95b8',
            'TITLE' => $this->title,
        ]);

        require_code('templates_internalise_screen');
        return internalise_own_screen($tpl);
    }

    /**
     * The UI to show a member's profile.
     *
     * @return Tempcode The UI
     */
    public function profile() : object
    {
        disable_php_memory_limit();

        if (($this->member_id_of == get_member()) && (get_param_string('id', null) !== null)) {
            unset($_GET['id']); // So self-URL links go without 'id', which is unneeded. oAuth may safelist what URLs may request linkage.
        }

        // Show validation messages when applicable
        $validated = $GLOBALS['FORUM_DRIVER']->get_member_row_field($this->member_id_of, 'm_validated');
        $_validated = get_param_integer('validated', 0);
        if ($validated == 0) {
            if (($_validated == 1) && (addon_installed('validation'))) {
                $validated = 1;
                attach_message(do_lang_tempcode('WILL_BE_VALIDATED_WHEN_SAVING'));
            }
        } elseif (($validated == 1) && ($_validated == 1)) {
            $action_log = build_url(['page' => 'admin_actionlog', 'type' => 'list', 'to_type' => 'VALIDATE_MEMBER', 'param_a' => strval($this->member_id_of)]);
            attach_message(do_lang_tempcode('ALREADY_VALIDATED', escape_html($action_log->evaluate())), 'notice');
        }

        require_code('cns_profiles');
        return render_profile_tabset($this->title, $this->member_id_of, get_member(), $this->username);
    }

    /**
     * The actualiser for unsubscribing from the newsletter.
     *
     * @return Tempcode The UI
     */
    public function newsletter_unsubscribe() : object
    {
        $id = get_param_integer('id');
        $hash = urldecode(get_param_string('hash'));

        $_subscriber = $GLOBALS['FORUM_DB']->query_select('f_members', ['*'], ['id' => $id], '', 1);
        if (!array_key_exists(0, $_subscriber)) {
            fatal_exit(do_lang_tempcode('INTERNAL_ERROR'));
        }
        $subscriber = $_subscriber[0];

        require_code('newsletter');

        if ($hash != get_unsubscribe_hash($subscriber['m_pass_hash_salted'])) {
            warn_exit(do_lang_tempcode('NEWSLETTER_COULD_NOT_UNSUBSCRIBE'));
        }

        $GLOBALS['FORUM_DB']->query_update('f_members', ['m_allow_emails_from_staff' => 0], ['id' => $id], '', 1);

        if (addon_installed('newsletter')) {
            $GLOBALS['SITE_DB']->query_delete('newsletter_subscribe', ['email' => $subscriber['m_email_address']]);
        }

        return inform_screen($this->title, do_lang_tempcode('newsletter:MEMBER_NEWSLETTER_UNSUBSCRIBED', escape_html(get_site_name())));
    }
}
