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

/**
 * Module page class.
 */
class Module_newsletter
{
    /**
     * 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'] = 14;
        $info['update_require_upgrade'] = true;
        $info['locked'] = false;
        $info['min_cms_version'] = 11.0;
        $info['addon'] = 'newsletter';
        return $info;
    }

    /**
     * Uninstall the module.
     */
    public function uninstall()
    {
        $tables = [
            'newsletter_subscribers',
            'newsletters',
            'newsletter_archive',
            'newsletter_subscribe',
            'newsletter_drip_send',
            'newsletter_periodic',
        ];
        $GLOBALS['SITE_DB']->drop_table_if_exists($tables);

        $values = [
            'newsletter_whatsnew',
            'newsletter_send_time',
        ];
        delete_values($values);

        delete_privilege('change_newsletter_subscriptions');
    }

    /**
     * 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) {
            require_lang('newsletter');

            $GLOBALS['SITE_DB']->create_table('newsletter_subscribers', [
                'id' => '*AUTO',
                'email' => 'SHORT_TEXT',
                'join_time' => 'TIME',
                'code_confirm' => 'INTEGER',
                'the_password' => 'SHORT_TEXT',
                'pass_salt' => 'SHORT_TEXT',
                'language' => 'ID_TEXT',
                'n_forename' => 'SHORT_TEXT',
                'n_surname' => 'SHORT_TEXT',
            ]);
            $GLOBALS['SITE_DB']->create_index('newsletter_subscribers', 'welcomemails', ['join_time']);
            $GLOBALS['SITE_DB']->create_index('newsletter_subscribers', 'code_confirm', ['code_confirm']);

            $GLOBALS['SITE_DB']->create_table('newsletter_archive', [
                'id' => '*AUTO',
                'date_and_time' => 'TIME',
                'subject' => 'SHORT_TEXT',
                'newsletter' => 'LONG_TEXT',
                'language' => 'ID_TEXT',
                'from_email' => 'SHORT_TEXT',
                'from_name' => 'SHORT_TEXT',
                'priority' => 'INTEGER',
                'template' => 'ID_TEXT',
                'html_only' => 'BINARY',
            ]);

            add_privilege('NEWSLETTER', 'change_newsletter_subscriptions', false);

            $GLOBALS['SITE_DB']->create_table('newsletters', [
                'id' => '*AUTO',
                'title' => 'SHORT_TRANS',
                'the_description' => 'LONG_TRANS',
            ]);

            $map = [];
            require_code('lang3');
            $map += lang_code_to_default_content('title', 'GENERAL');
            $map += lang_code_to_default_content('the_description', 'NEWSLETTER_GENERAL');
            $GLOBALS['SITE_DB']->query_insert('newsletters', $map);

            $GLOBALS['SITE_DB']->create_table('newsletter_subscribe', [
                'newsletter_id' => '*AUTO_LINK',
                'email' => '*SHORT_TEXT',
            ], false, false, true);

            $GLOBALS['SITE_DB']->create_table('newsletter_drip_send', [
                'id' => '*AUTO',
                'd_inject_time' => 'TIME',
                'd_message_id' => 'AUTO_LINK',
                'd_message_binding' => 'LONG_TEXT',
                'd_to_email' => 'SHORT_TEXT',
                'd_to_name' => 'SHORT_TEXT',
            ]);
        }

        if (($upgrade_from === null) || ($upgrade_from < 9)) {
            $GLOBALS['SITE_DB']->create_table('newsletter_periodic', [
                'id' => '*AUTO',
                'np_message' => 'LONG_TEXT',
                'np_subject' => 'LONG_TEXT',
                'np_lang' => 'LANGUAGE_NAME',
                'np_send_details' => 'LONG_TEXT',
                'np_html_only' => 'BINARY',
                'np_from_email' => 'SHORT_TEXT',
                'np_from_name' => 'SHORT_TEXT',
                'np_priority' => 'SHORT_INTEGER',
                'np_spreadsheet_data' => 'LONG_TEXT',
                'np_frequency' => 'SHORT_TEXT',
                'np_day' => 'SHORT_INTEGER',
                'np_in_full' => 'BINARY',
                'np_template' => 'ID_TEXT',
                'np_last_sent_time' => 'TIME',
            ]);
        }

        if (($upgrade_from !== null) && ($upgrade_from < 9)) { // LEGACY
            $GLOBALS['SITE_DB']->add_table_field('newsletter_drip_send', 'd_template', 'ID_TEXT');
        }

        if (($upgrade_from !== null) && ($upgrade_from < 11)) { // LEGACY
            $GLOBALS['SITE_DB']->rename_table('newsletter', 'newsletter_subscribers');

            $GLOBALS['SITE_DB']->alter_table_field('newsletter_subscribers', 'the_password', 'SHORT_TEXT');
        }

        if (($upgrade_from !== null) && ($upgrade_from < 13)) { // LEGACY
            // We've switched to JSON for spreadsheet data
            $GLOBALS['SITE_DB']->alter_table_field('newsletter_periodic', 'np_csv_data', 'LONG_TEXT', 'np_spreadsheet_data');
            $periodic = $GLOBALS['SITE_DB']->query_select('newsletter_periodic', ['id', 'np_spreadsheet_data']);
            foreach ($periodic as $p) {
                if ($p['np_spreadsheet_data'] != '') {
                    $GLOBALS['SITE_DB']->query_update('newsletter_periodic', ['np_spreadsheet_data' => json_encode(unserialize($p['np_spreadsheet_data']))], ['id' => $p['id']], '', 1);
                }
            }

            // We've dropped interest levels
            $GLOBALS['SITE_DB']->delete_table_field('newsletter_archive', 'importance_level');
            $GLOBALS['SITE_DB']->delete_table_field('newsletter_subscribe', 'the_level');

            // These fields have been renamed and moved to newsletter_archive
            $GLOBALS['SITE_DB']->delete_index_if_exists('newsletter_drip_send', 'd_to_email');
            $GLOBALS['SITE_DB']->delete_index_if_exists('newsletter_drip_send', 'd_inject_time');
            $GLOBALS['SITE_DB']->delete_table_field('newsletter_drip_send', 'd_subject');
            $GLOBALS['SITE_DB']->delete_table_field('newsletter_drip_send', 'd_from_email');
            $GLOBALS['SITE_DB']->delete_table_field('newsletter_drip_send', 'd_from_name');
            $GLOBALS['SITE_DB']->delete_table_field('newsletter_drip_send', 'd_priority');
            $GLOBALS['SITE_DB']->delete_table_field('newsletter_drip_send', 'd_template');
            $GLOBALS['SITE_DB']->delete_table_field('newsletter_drip_send', 'd_html_only');
            $GLOBALS['SITE_DB']->delete_table_field('newsletter_drip_send', 'd_message');

            $GLOBALS['SITE_DB']->add_table_field('newsletter_archive', 'from_email', 'SHORT_TEXT');
            $GLOBALS['SITE_DB']->add_table_field('newsletter_archive', 'from_name', 'SHORT_TEXT');
            $GLOBALS['SITE_DB']->add_table_field('newsletter_archive', 'priority', 'INTEGER');
            $GLOBALS['SITE_DB']->add_table_field('newsletter_archive', 'template', 'ID_TEXT');
            $GLOBALS['SITE_DB']->add_table_field('newsletter_archive', 'html_only', 'BINARY');
            $GLOBALS['SITE_DB']->add_table_field('newsletter_drip_send', 'd_message_id', 'AUTO_LINK');
            $GLOBALS['SITE_DB']->add_table_field('newsletter_drip_send', 'd_message_binding', 'LONG_TEXT');

            $GLOBALS['SITE_DB']->alter_table_field('newsletters', 'description', 'LONG_TRANS', 'the_description');
            $GLOBALS['SITE_DB']->alter_table_field('newsletter_subscribers', 'pass_salt', 'SHORT_TEXT');

            $GLOBALS['SITE_DB']->create_index('newsletter_drip_send', 'd_message_id', ['d_message_id']);
        }

        if (($upgrade_from === null) || ($upgrade_from < 12)) {
            $GLOBALS['SITE_DB']->create_index('newsletter_subscribers', 'email', ['email']);
        }

        if (($upgrade_from !== null) && ($upgrade_from < 14)) { // LEGACY: 11.beta1
            // Database consistency fixes
            $GLOBALS['SITE_DB']->alter_table_field('newsletter_archive', 'date_and_time', 'TIME');
            $GLOBALS['SITE_DB']->alter_table_field('newsletter_periodic', 'np_last_sent', 'TIME', 'np_last_sent_time');
        }
    }

    /**
     * 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 (!addon_installed('newsletter')) {
            return null;
        }

        if ($check_perms) {
            if ($GLOBALS['SITE_DB']->query_select_value('newsletters', 'COUNT(*)') == 0) {
                return [];
            }
        }
        return [
            'browse' => ['NEWSLETTER_JOIN', 'menu/site_meta/newsletters'],
        ];
    }

    public $title;

    /**
     * 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
    {
        $error_msg = new Tempcode();
        if (!addon_installed__messaged('newsletter', $error_msg)) {
            return $error_msg;
        }

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

        require_lang('newsletter');

        if ($type == 'browse') {
            $this->title = get_screen_title('_NEWSLETTER_JOIN', true, [escape_html(get_option('newsletter_title'))]);
            breadcrumb_set_self(do_lang_tempcode('NEWSLETTER'));
        }

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

        if ($type == 'reset') {
            breadcrumb_set_self(do_lang_tempcode('NEWSLETTER_PASSWORD_BEEN_RESET'));
            breadcrumb_set_parents([['_SELF:_SELF:browse', do_lang_tempcode('NEWSLETTER')]]);

            $this->title = get_screen_title(get_option('newsletter_title'), false);
        }

        if ($type == 'confirm') {
            breadcrumb_set_parents([['_SELF:_SELF:browse', do_lang_tempcode('NEWSLETTER')]]);

            $this->title = get_screen_title(get_option('newsletter_title'), false);
        }

        if ($type == 'do') {
            breadcrumb_set_parents([['_SELF:_SELF:browse', do_lang_tempcode('NEWSLETTER')]]);

            $this->title = get_screen_title('_NEWSLETTER_JOIN', true, [escape_html(get_option('newsletter_title'))]);
        }

        return null;
    }

    /**
     * Execute the module.
     *
     * @return Tempcode The result of execution
     */
    public function run() : object
    {
        require_code('newsletter');

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

        if ($type == 'browse') {
            return $this->newsletter_form();
        }
        if ($type == 'confirm') {
            return $this->newsletter_confirm_joining();
        }
        if ($type == 'do') {
            return $this->newsletter_maintenance();
        }
        if ($type == 'reset') {
            return $this->newsletter_password_reset();
        }
        if ($type == 'unsub') {
            return $this->newsletter_unsubscribe();
        }

        return new Tempcode();
    }

    /**
     * The UI to sign up to the newsletter (actually, generally manage subscription).
     *
     * @return Tempcode The UI
     */
    public function newsletter_form() : object
    {
        $newsletters = $GLOBALS['SITE_DB']->query_select('newsletters', ['*']);
        if (empty($newsletters)) {
            inform_exit(do_lang_tempcode('NO_CATEGORIES'));
        }

        $post_url = build_url(['page' => '_SELF', 'type' => 'do'], '_SELF');
        $submit_name = do_lang_tempcode('NEWSLETTER_JOIN');

        require_code('form_templates');

        url_default_parameters__enable();

        $forename = '';
        $surname = '';
        if (!is_guest()) {
            $their_email = get_param_string('email', $GLOBALS['FORUM_DRIVER']->get_member_email_address(get_member()), INPUT_FILTER_GET_IDENTIFIER);
            $username = $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true);
            $parts = explode(' ', $username);
            if (count($parts) >= 2) {
                $surname = array_pop($parts);
                $forename = implode(' ', $parts);
            }

            /*
            Insecure, potential information leak
            $existing_record = $GLOBALS['SITE_DB']->query_select('newsletter_subscribers', ['n_forename', 'n_surname'], ['email' => $their_email], '', 1);
            if (array_key_exists(0, $existing_record)) {
                $forename = $existing_record[0]['n_forename'];
                $surname = $existing_record[0]['n_surname'];
            }*/
        } else {
            $their_email = get_param_string('email', '', INPUT_FILTER_GET_IDENTIFIER);
        }

        $message = get_option('newsletter_text');
        if (has_actual_page_access(get_member(), 'admin_config')) {
            if ($message != '') {
                $message .= ' [semihtml]<span class="associated-link"><a href="{$PAGE_LINK*,_SEARCH:admin_config:category:FEATURE#group_NEWSLETTER}">' . do_lang('EDIT') . '</a></span>[/semihtml]';
            }
        }
        $text = comcode_to_tempcode($message, null, true);

        // Build up the join form
        $fields = new Tempcode();
        $fields->attach(form_input_email(do_lang_tempcode('EMAIL_ADDRESS'), do_lang_tempcode('DESCRIPTION_SUBSCRIBE_ADDRESS'), 'email', $their_email, true));
        $fields->attach(form_input_line(do_lang_tempcode('FORENAME'), '', 'forename', $forename, false));
        $fields->attach(form_input_line(do_lang_tempcode('SURNAME'), '', 'surname', $surname, false));
        $fields->attach(form_input_password(do_lang_tempcode('YOUR_PASSWORD'), do_lang_tempcode('DESCRIPTION_MAINTENANCE_PASSWORD'), 'password', false));
        $fields->attach(form_input_password(do_lang_tempcode('CONFIRM_PASSWORD'), '', 'password_confirm', false));
        if (count(find_all_langs()) != 1) {
            $fields->attach(form_input_list(do_lang_tempcode('LANGUAGE'), '', 'lang', create_selection_list_langs(user_lang())));
        }
        $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', ['_GUID' => 'a87e4be6cbc070e66e25ad4ece429cc4', 'TITLE' => do_lang_tempcode('NEWSLETTER_SUBSCRIPTIONS')]));
        foreach ($newsletters as $newsletter) {
            $newsletter_title = get_translated_text($newsletter['title']);
            $newsletter_description = get_translated_text($newsletter['the_description']);
            $GLOBALS['NO_DEV_MODE_FULLSTOP_CHECK'] = true;
            $fields->attach(form_input_tick(do_lang_tempcode('SUBSCRIBE_TO', make_string_tempcode(escape_html($newsletter_title))), make_string_tempcode(escape_html($newsletter_description)), 'subscribe' . strval($newsletter['id']), true));
        }

        $hidden = new Tempcode();

        if (addon_installed('captcha')) {
            require_code('captcha');
            if (use_captcha()) {
                $fields->attach(form_input_captcha($hidden));
                $text->attach(' ');
                $text->attach(do_lang_tempcode('captcha:FORM_TIME_SECURITY'));
            }
        }

        url_default_parameters__disable();

        $text->attach(paragraph(do_lang_tempcode('CHANGE_SETTINGS_BY_RESUBSCRIBING')));

        $js_function_calls = ['newsletterNewsletterForm'];
        if ((function_exists('captcha_ajax_check_function')) && (captcha_ajax_check_function() != '')) {
            $js_function_calls[] = captcha_ajax_check_function();
        }

        require_javascript('newsletter');
        return do_template('FORM_SCREEN', [
            '_GUID' => '24d7575465152f450c5a8e62650bf6c8',
            'JS_FUNCTION_CALLS' => $js_function_calls,
            'HIDDEN' => $hidden,
            'FIELDS' => $fields,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
            'URL' => $post_url,
            'TITLE' => $this->title,
            'TEXT' => $text,
            'ANALYTIC_EVENT_CATEGORY' => do_lang('NEWSLETTER_JOIN'),
        ]);
    }

    /**
     * The actualiser for newsletter subscription maintenance (adding, updating, deleting).
     *
     * @return Tempcode The UI
     */
    public function newsletter_maintenance() : object
    {
        require_code('type_sanitisation');
        require_code('crypt');
        require_code('newsletter2');

        if (addon_installed('captcha')) {
            require_code('captcha');
            enforce_captcha();
        }

        // Add
        $email = post_param_string('email', false, INPUT_FILTER_POST_IDENTIFIER);
        $password = post_param_string('password', '', INPUT_FILTER_PASSWORD);
        $forename = post_param_string('forename');
        $surname = post_param_string('surname');
        if ($password != post_param_string('password_confirm', '', INPUT_FILTER_PASSWORD)) {
            warn_exit(make_string_tempcode(escape_html(do_lang('PASSWORD_MISMATCH'))));
        }
        $language = user_lang__with__translation_override(true);
        if (!is_valid_email_address($email)) {
            return warn_screen($this->title, do_lang_tempcode('IMPROPERLY_FILLED_IN'));
        }

        $message = do_lang_tempcode('NEWSLETTER_UPDATE');
        $old_confirm = $GLOBALS['SITE_DB']->query_select_value_if_there('newsletter_subscribers', 'code_confirm', ['email' => $email]);

        // New (or as new - replace old unconfirmed records)
        if (($old_confirm === null) || ($old_confirm != 0)) {
            // As it is new we need to actually confirm you were setting some subscription settings
            $newsletters = $GLOBALS['SITE_DB']->query_select('newsletters', ['id']);
            $found_subscription = false;
            foreach ($newsletters as $newsletter) {
                if (post_param_integer('subscribe' . strval($newsletter['id']), 0) == 1) {
                    $found_subscription = true;
                }
            }
            if (!$found_subscription) {
                // No subscription settings
                warn_exit(do_lang_tempcode('NOT_NEWSLETTER_SUBSCRIBER'));
            }

            $code_confirm = ($old_confirm === null) ? get_secure_random_number() : $old_confirm;
            if ($password == '') {
                $password = get_secure_random_password(null, $forename . ' ' . $surname, $email);
            }
            $salt = get_secure_random_string(32, CRYPT_BASE64);
            if ($old_confirm === null) {
                add_newsletter_subscriber($email, time(), $code_confirm, ratchet_hash($password, $salt), $salt, $language, $forename, $surname);

                if (addon_installed('stats')) {
                    require_code('stats');
                    log_stats_event('newsletter:__NEWSLETTER_JOIN');
                }

                $this->_send_confirmation($email, $code_confirm, $password, $forename, $surname);
            } else {
                $id = $GLOBALS['SITE_DB']->query_select_value('newsletter_subscribers', 'id', ['email' => $email]);
                edit_newsletter_subscriber($id, $email, time(), null, null, null, $language, $forename, $surname);

                $this->_send_confirmation($email, $code_confirm, null, $forename, $surname);
            }
            $message = do_lang_tempcode('NEWSLETTER_CONFIRM', escape_html($email));
        }

        // Existing, OR it is new and we are just proceeding to save the subscription settings...

        // Change/make settings
        $old_password = $GLOBALS['SITE_DB']->query_select_value('newsletter_subscribers', 'the_password', ['email' => $email]);
        $old_salt = $GLOBALS['SITE_DB']->query_select_value('newsletter_subscribers', 'pass_salt', ['email' => $email]);
        require_code('crypt');
        if ((!has_privilege(get_member(), 'change_newsletter_subscriptions')) && ($old_confirm !== null) && ($old_confirm == 0) && ($old_password != '') && (!ratchet_hash_verify($password, $old_password, $old_salt, CRYPT_LEGACY_FALLBACK_TO_V10))) { // Access denied. People who can change any subscriptions can't get denied.
            // Access denied to an existing record that was confirmed
            $_reset_url = build_url(['page' => '_SELF', 'type' => 'reset', 'email' => $email], '_SELF');
            $reset_url = $_reset_url->evaluate();
            return warn_screen($this->title, do_lang_tempcode('NEWSLETTER_PASSWORD_RESET', escape_html($reset_url)));
        } else {
            // Access granted, make edit
            $newsletters = $GLOBALS['SITE_DB']->query_select('newsletters', ['id']);
            foreach ($newsletters as $newsletter) {
                $subscribe = (post_param_integer('subscribe' . strval($newsletter['id']), 0) == 1);

                // First we delete
                $GLOBALS['SITE_DB']->query_delete('newsletter_subscribe', ['newsletter_id' => $newsletter['id'], 'email' => $email], '', 1);
                if ($subscribe) {
                    $GLOBALS['SITE_DB']->query_insert('newsletter_subscribe', ['newsletter_id' => $newsletter['id'], 'email' => $email]);
                }
            }

            // Update name etc if it's an edit
            if (($old_confirm !== null) && ($old_confirm == 0)) {
                $id = $GLOBALS['SITE_DB']->query_select_value('newsletter_subscribers', 'id', ['email' => $email]);
                edit_newsletter_subscriber($id, $email, null, null, null, null, null, $forename, $surname);
            }
        }

        // Done, show result
        return inform_screen($this->title, $message);
    }

    /**
     * The actualiser for resetting newsletter password.
     *
     * @return Tempcode The UI
     */
    public function newsletter_password_reset() : object
    {
        require_code('crypt');

        $email = get_param_string('email', false, INPUT_FILTER_GET_IDENTIFIER);
        $language = $GLOBALS['SITE_DB']->query_select_value('newsletter_subscribers', 'language', ['email' => $email]);
        $salt = get_secure_random_string(32, CRYPT_BASE64);
        $new_password = get_secure_random_password(null, '', $email);
        $GLOBALS['SITE_DB']->query_update('newsletter_subscribers', ['pass_salt' => $salt, 'the_password' => ratchet_hash($new_password, $salt)], ['email' => $email], '', 1);

        $message = do_lang('NEWSLETTER_PASSWORD_CHANGE', comcode_escape(get_ip_address()), comcode_escape($new_password), null, $language);

        require_code('mail');
        dispatch_mail(get_option('newsletter_title'), $message, [$email], $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true));

        return inform_screen($this->title, protect_from_escaping(do_lang('NEWSLETTER_PASSWORD_BEEN_RESET', null, null, null, $language)));
    }

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

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

        if (!verify_unsunscribe_hash($hash, $subscriber['the_password'])) {
            warn_exit(do_lang_tempcode('NEWSLETTER_COULD_NOT_UNSUBSCRIBE'));
        }

        $GLOBALS['SITE_DB']->query_delete('newsletter_subscribe', ['email' => $subscriber['email']]);

        if (get_forum_type() == 'cns') {
            // Do a simple one-to-one-mapping if appropriate
            if ($GLOBALS['SITE_DB']->query_select_value('newsletters', 'COUNT(*)') == 1) {
                $GLOBALS['FORUM_DB']->query_update('f_members', ['m_allow_emails_from_staff' => 0], ['m_email_address' => $subscriber['email']], '', 1);
            }
        }

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

    /**
     * Send a newsletter join confirmation.
     *
     * @param  SHORT_TEXT $email The e-mail address
     * @param  integer $code_confirm The confirmation code
     * @param  ?SHORT_TEXT $password The newsletter password (null: password may not be viewed, because it's been permanently hashed already)
     * @param  string $forename Subscribers forename
     * @param  string $surname Subscribers surname
     */
    public function _send_confirmation(string $email, int $code_confirm, ?string $password, string $forename, string $surname)
    {
        if ($password === null) {
            $password = do_lang('NEWSLETTER_PASSWORD_ENCRYPTED');
        }

        $_url = build_url(['page' => 'newsletter', 'type' => 'confirm', 'email' => $email, 'confirm' => $code_confirm], '_SELF', [], false, true);
        $url = $_url->evaluate();
        $newsletter_url = build_url(['page' => '_SELF'], '_SELF');
        $message = do_lang('NEWSLETTER_SIGNUP_TEXT', comcode_escape($url), comcode_escape($password), [$forename, $surname, $email, get_site_name(), $newsletter_url->evaluate()]);

        require_code('mail');
        dispatch_mail(do_lang('NEWSLETTER_SIGNUP'), $message, [$email], $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true));
    }

    /**
     * The UI for having confirmed an e-mail address onto the newsletter.
     *
     * @return Tempcode The UI
     */
    public function newsletter_confirm_joining() : object
    {
        $code_confirm = get_param_integer('confirm');
        $email = get_param_string('email', false, INPUT_FILTER_GET_IDENTIFIER);
        $correct_confirm = $GLOBALS['SITE_DB']->query_select_value_if_there('newsletter_subscribers', 'code_confirm', ['email' => $email]);
        if ($correct_confirm === null) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
        }
        if ($correct_confirm == $code_confirm) {
            $GLOBALS['SITE_DB']->query_update('newsletter_subscribers', ['code_confirm' => 0], ['email' => $email], '', 1);
            return inform_screen($this->title, do_lang_tempcode('NEWSLETTER_CONFIRMED'));
        }

        return warn_screen($this->title, do_lang_tempcode(($correct_confirm == 0) ? 'ALREADY_CONFIRMED' : 'INCORRECT_CONFIRMATION'));
    }
}
