<?php /*

 Composr
 Copyright (c) ocProducts, 2004-2016

 See text/EN/licence.txt for full licencing 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  ocProducts Ltd
 * @package    newsletter
 */

require_code('crud_module');

/**
 * Module page class.
 */
class Module_admin_newsletter extends Standard_crud_module
{
    public $lang_type = 'NEWSLETTER';
    public $select_name = 'TITLE';
    public $do_preview = null;
    public $cache_level_counts;
    public $menu_label = 'NEWSLETTER';
    public $table = 'newsletters';
    public $title_is_multi_lang = true;
    public $donext_entry_content_type = 'newsletter';
    public $donext_category_content_type = null;

    /**
     * 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($check_perms = true, $member_id = null, $support_crosslinks = true, $be_deferential = false)
    {
        $ret = array(
            'browse' => array('MANAGE_NEWSLETTER', 'menu/site_meta/newsletters'),
            'new' => array('NEWSLETTER_SEND', 'menu/site_meta/newsletters'),
            'whatsnew' => array('WHATSNEW', 'menu/adminzone/tools/newsletter/newsletter_from_changes'),
            'subscribers' => array('VIEW_NEWSLETTER_SUBSCRIBERS', 'menu/adminzone/tools/newsletter/subscribers'),
            'import_subscribers' => array('IMPORT_NEWSLETTER_SUBSCRIBERS', 'menu/adminzone/tools/newsletter/import_subscribers'),
            'archive' => array('NEWSLETTER_ARCHIVE', 'menu/_generic_admin/view_archive'),
        );
        if (!GOOGLE_APPENGINE) {
            $ret['bounce_filter_a'] = array('BOUNCE_FILTER', 'menu/adminzone/tools/newsletter/newsletter_email_bounce');
        }
        $ret += parent::get_entry_points();
        return $ret;
    }

    public $title;

    /**
     * Module pre-run function. Allows us to know metadata for <head> before we start streaming output.
     *
     * @param  boolean $top_level Whether this is running at the top level, prior to having sub-objects called.
     * @param  ?ID_TEXT $type The screen type to consider for metadata purposes (null: read from environment).
     * @return ?Tempcode Tempcode indicating some kind of exceptional output (null: none).
     */
    public function pre_run($top_level = true, $type = null)
    {
        $type = get_param_string('type', 'browse');

        require_lang('newsletter');

        set_helper_panel_tutorial('tut_newsletter');

        if ($type == 'confirm') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:new', do_lang_tempcode('NEWSLETTER_SEND'))));
            breadcrumb_set_self(do_lang_tempcode('CONFIRM'));
        }

        if ($type == 'send') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:new', do_lang_tempcode('NEWSLETTER_SEND'))));
            breadcrumb_set_self(do_lang_tempcode('DONE'));
        }

        if ($type == 'view') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:archive', do_lang_tempcode('NEWSLETTER_ARCHIVE'))));
            breadcrumb_set_self(do_lang_tempcode('VIEW'));
        }

        if ($type == 'import_subscribers') {
            if (either_param_integer('level', null) === 0) {
                $this->title = get_screen_title('SOMETHING_NEWSLETTER_SUBSCRIBERS'); // Don't say import, so as to not confuse people given a pre-set link to unsubscribe people from
            } else {
                $this->title = get_screen_title('IMPORT_NEWSLETTER_SUBSCRIBERS');
            }
        }

        if ($type == 'bounce_filter_a' || $type == 'bounce_filter_b' || $type == 'bounce_filter_c' || $type == 'bounce_filter_d') {
            $this->title = get_screen_title('BOUNCE_FILTER');
        }

        if ($type == 'subscribers') {
            $this->title = get_screen_title('VIEW_NEWSLETTER_SUBSCRIBERS');
        }

        if ($type == 'whatsnew' || $type == 'whatsnew_2') {
            $this->title = get_screen_title('WHATSNEW');
        }

        if ($type == 'new' || $type == 'confirm' || $type == 'send' || $type == 'whatsnew_3') {
            $this->title = get_screen_title('NEWSLETTER_SEND');
        }

        if ($type == 'archive') {
            $this->title = get_screen_title('NEWSLETTER_ARCHIVE');
        }

        if (either_param_integer('csv', 0) == 1) {
            $GLOBALS['OUTPUT_STREAMING'] = false; // Too complex to do a pre_run for this properly
        }

        return parent::pre_run($top_level);
    }

    /**
     * Standard crud_module run_start.
     *
     * @param  ID_TEXT $type The type of module execution
     * @return Tempcode The output of the run
     */
    public function run_start($type)
    {
        $GLOBALS['NO_QUERY_LIMIT'] = true;

        require_code('newsletter');
        require_css('newsletter');

        $this->cache_level_counts = array();

        $this->extra_donext_entries = array(
            array('menu/site_meta/newsletters', array('_SELF', array('type' => 'new'), '_SELF'), do_lang('NEWSLETTER_SEND')),
            array('menu/adminzone/tools/newsletter/newsletter_from_changes', array('_SELF', array('type' => 'whatsnew'), '_SELF'), do_lang('WHATSNEW'), 'DOC_WHATSNEW'),
            array('menu/_generic_admin/view_archive', array('_SELF', array('type' => 'archive'), '_SELF'), do_lang('NEWSLETTER_ARCHIVE')),
            array('menu/adminzone/tools/newsletter/subscribers', array('_SELF', array('type' => 'subscribers'), '_SELF'), do_lang('VIEW_SUBSCRIBERS')),
            array('menu/adminzone/tools/newsletter/import_subscribers', array('_SELF', array('type' => 'import_subscribers'), '_SELF'), do_lang('IMPORT_NEWSLETTER_SUBSCRIBERS')),
        );

        if (!GOOGLE_APPENGINE) {
            $this->extra_donext_entries[] = array('menu/adminzone/tools/newsletter/newsletter_email_bounce', array('_SELF', array('type' => 'bounce_filter_a'), '_SELF'), do_lang('BOUNCE_FILTER'));
        }

        $this->add_one_label = do_lang_tempcode('ADD_NEWSLETTER');
        $this->edit_this_label = do_lang_tempcode('EDIT_THIS_NEWSLETTER');
        $this->edit_one_label = do_lang_tempcode('EDIT_NEWSLETTER');

        $this->add_text = do_lang_tempcode('HELP_ADD_NEWSLETTER', escape_html(static_evaluate_tempcode(build_url(array('page' => '_SELF', 'type' => 'new'), '_SELF'))));

        if ($type == 'browse') {
            return $this->browse();
        }

        if ($type == 'import_subscribers') {
            return $this->import_subscribers();
        }
        if ($type == 'subscribers') {
            return $this->view_subscribers();
        }

        if ($type == 'whatsnew') {
            return $this->whatsnew_1();
        }
        if ($type == 'whatsnew_2') {
            return $this->whatsnew_2();
        }
        if ($type == 'whatsnew_3') {
            return $this->whatsnew_3();
        }
        if ($type == 'new') {
            return $this->send_gui();
        }
        if ($type == 'confirm') {
            return $this->confirm_send();
        }
        if ($type == 'send') {
            return $this->send_message();
        }

        if ($type == 'archive') {
            return $this->archive();
        }
        if ($type == 'view') {
            return $this->view();
        }
        if ($type == 'new') {
            return $this->send_gui();
        }

        if (!GOOGLE_APPENGINE) {
            if ($type == 'bounce_filter_a') {
                return $this->bounce_filter_a();
            }
            if ($type == 'bounce_filter_b') {
                return $this->bounce_filter_b();
            }
            if ($type == 'bounce_filter_c') {
                return $this->bounce_filter_c();
            }
            if ($type == 'bounce_filter_d') {
                return $this->bounce_filter_d();
            }
        }

        return new Tempcode();
    }

    /**
     * The do-next manager for before content management.
     *
     * @return Tempcode The UI
     */
    public function browse()
    {
        $num_in_queue = $GLOBALS['SITE_DB']->query_select_value('newsletter_drip_send', 'COUNT(*)');
        if ($num_in_queue > 0) {
            attach_message(do_lang_tempcode('NEWSLETTER_DRIP_SEND_QUEUE', escape_html(integer_format($num_in_queue))), 'inform');
        }

        require_code('templates_donext');
        return do_next_manager(get_screen_title('MANAGE_NEWSLETTER'), comcode_lang_string('DOC_NEWSLETTER'),
            array_merge(array(
                array('menu/_generic_admin/add_one', array('_SELF', array('type' => 'add'), '_SELF'), do_lang('ADD_NEWSLETTER')),
                array('menu/_generic_admin/edit_one', array('_SELF', array('type' => 'edit'), '_SELF'), do_lang('EDIT_NEWSLETTER')),
            ), $this->extra_donext_entries),
            do_lang('MANAGE_NEWSLETTER')
        );
    }

    /**
     * Count the number of users on a certain level and language of the newsletter.
     *
     * @param  AUTO_LINK $id The newsletter
     * @param  integer $level The newsletter level
     * @range  -1 5
     * @param  ID_TEXT $lang The language
     * @return integer The count
     */
    public function _count_level($id, $level, $lang)
    {
        $map = array();
        $map[strval($id)] = $level;
        $results = newsletter_who_send_to($map, $lang, 0, 0, false, '', true);
        return $results[6][strval($id)];
    }

    /**
     * The UI to import subscribers into the newsletter.
     *
     * @return Tempcode The UI
     */
    public function import_subscribers()
    {
        $_language = choose_language($this->title);
        if (is_object($_language)) {
            return $_language;
        }

        require_lang('cns');

        $newsletter_id = post_param_string('id', null);
        $level = post_param_integer('level', 4);

        // Select newsletter and attach CSV
        if (is_null($newsletter_id)) {
            $default_newsletter_id = get_param_integer('id', db_get_first_id());
            $default_level = get_param_integer('level', 4);

            $fields = new Tempcode();
            $hidden = new Tempcode();
            require_code('form_templates');

            // Selection
            $newsletters = new Tempcode();
            $rows = $GLOBALS['SITE_DB']->query_select('newsletters', array('id', 'title'));
            foreach ($rows as $newsletter) {
                $newsletters->attach(form_input_list_entry(strval($newsletter['id']), $newsletter['id'] === $default_newsletter_id, get_translated_text($newsletter['title'])));
            }
            if (get_forum_type() == 'cns') {
                $newsletters->attach(form_input_list_entry('-1', -1 === $default_newsletter_id, do_lang_tempcode('NEWSLETTER_CNS')));
            }
            if ($newsletters->is_empty()) {
                inform_exit(do_lang_tempcode('NO_CATEGORIES'));
            }
            if (count($rows) == 0) {
                $hidden->attach(form_input_hidden('id', '-1'));
            } else {
                $fields->attach(form_input_list(do_lang_tempcode('NEWSLETTER'), '', 'id', $newsletters, null, true));
            }
            $fields->attach(form_input_upload(do_lang_tempcode('UPLOAD'), do_lang_tempcode('DESCRIPTION_UPLOAD_CSV_2'), 'file', true, null, null, true, 'csv,txt'));
            // Choose level
            if (get_option('interest_levels') == '0') {
                $l = new Tempcode();
                $l->attach(form_input_list_entry('0', 0 == $default_level, do_lang_tempcode('NEWSLETTER_0')));
                $l->attach(form_input_list_entry('4', 4 == $default_level, do_lang_tempcode('NEWSLETTER_IMPORT')));
                $fields->attach(form_input_list(do_lang_tempcode('SUBSCRIPTION_STATUS'), do_lang_tempcode('DESCRIPTION_SUBSCRIPTION_LEVEL_3'), 'level', $l));
            } else {
                $l = new Tempcode();
                $l->attach(form_input_list_entry('0', 0 == $default_level, do_lang_tempcode('NEWSLETTER_0')));
                $l->attach(form_input_list_entry('1', 1 == $default_level, do_lang_tempcode('NEWSLETTER_1')));
                $l->attach(form_input_list_entry('2', 2 == $default_level, do_lang_tempcode('NEWSLETTER_2')));
                $l->attach(form_input_list_entry('3', 3 == $default_level, do_lang_tempcode('NEWSLETTER_3')));
                $l->attach(form_input_list_entry('4', 4 == $default_level, do_lang_tempcode('NEWSLETTER_4')));
                $fields->attach(form_input_list(do_lang_tempcode('SUBSCRIPTION_LEVEL'), do_lang_tempcode('DESCRIPTION_SUBSCRIPTION_LEVEL_2'), 'level', $l));
            }

            $submit_name = do_lang_tempcode('PROCEED');
            $post_url = get_self_url();

            $hidden->attach(form_input_hidden('lang', $_language));
            handle_max_file_size($hidden);

            return do_template('FORM_SCREEN', array('_GUID' => '7e0387bcc4a1b7e2846ba357d36dbc15', 'SKIP_WEBSTANDARDS' => true, 'HIDDEN' => $hidden, 'TITLE' => $this->title, 'TEXT' => '', 'FIELDS' => $fields, 'SUBMIT_ICON' => 'menu___generic_admin__import', 'SUBMIT_NAME' => $submit_name, 'URL' => $post_url));
        }

        // Read data
        require_code('uploads');
        if (((is_plupload(true)) && (array_key_exists('file', $_FILES))) || ((array_key_exists('file', $_FILES)) && (is_uploaded_file($_FILES['file']['tmp_name'])))) {
            $target_path = get_custom_file_base() . '/safe_mode_temp/' . basename($_FILES['file']['tmp_name']);
            require_code('files2');
            if (!file_exists(dirname($target_path))) {
                make_missing_directory(dirname($target_path));
            }
            copy($_FILES['file']['tmp_name'], $target_path);
            fix_permissions($target_path);
            sync_file($target_path);
        } else {
            warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN_UPLOAD'));
        }

        if (either_param_integer('level', null) === 0) {
            $action_title = do_lang('SOMETHING_NEWSLETTER_SUBSCRIBERS'); // Don't say import, so as to not confuse people given a pre-set link to unsubscribe people from
        } else {
            $action_title = do_lang('IMPORT_NEWSLETTER_SUBSCRIBERS');
        }

        require_code('tasks');
        return call_user_func_array__long_task($action_title, $this->title, 'import_newsletter_subscribers', array($_language, $newsletter_id, $level, $target_path));
    }

    /**
     * The UI to select an IMAP server for bounce filtering.
     *
     * @return Tempcode The UI
     */
    public function bounce_filter_a()
    {
        if (!function_exists('imap_open')) {
            warn_exit(do_lang_tempcode('IMAP_NEEDED'));
        }

        $fields = new Tempcode();
        require_code('form_templates');

        url_default_parameters__enable();

        $fields->attach(form_input_line(do_lang_tempcode('HOST'), new Tempcode(), 'server', get_option('imap_host'), true));
        $fields->attach(form_input_line(do_lang_tempcode('USERNAME'), new Tempcode(), 'username', get_option('imap_username'), true));
        $fields->attach(form_input_password(do_lang_tempcode('PASSWORD'), new Tempcode(), 'password', true, null, get_option('imap_password')));
        $fields->attach(form_input_integer(do_lang_tempcode('PORT'), new Tempcode(), 'port', intval(get_option('imap_port')), true));

        url_default_parameters__disable();

        $submit_name = do_lang_tempcode('PROCEED');
        $post_url = get_self_url();

        $post_url = build_url(array('page' => '_SELF', 'type' => 'bounce_filter_b'), '_SELF');
        return do_template('FORM_SCREEN', array('_GUID' => '87f79d177931bab13f614b9cb24fb877', 'SKIP_WEBSTANDARDS' => true, 'HIDDEN' => '', 'TITLE' => $this->title, 'TEXT' => do_lang_tempcode('ENTER_IMAP_DETAILS'), 'FIELDS' => $fields, 'SUBMIT_ICON' => 'buttons__proceed', 'SUBMIT_NAME' => $submit_name, 'URL' => $post_url));
    }

    /**
     * The UI to select an inbox for bounce filtering.
     *
     * @return Tempcode The UI
     */
    public function bounce_filter_b()
    {
        require_code('mail2');

        require_code('form_templates');

        $username = post_param_string('username');
        $password = post_param_string('password');
        $server = post_param_string('server');
        $port = post_param_integer('port');

        $_folders = find_mail_folders($server, $port, $username, $password);

        $folders = new Tempcode();
        foreach ($_folders as $folder => $label) {
            $selected = ((get_option('imap_folder') != '') && (get_option('imap_folder') == $folder)) || ((get_option('imap_folder') == '') && (stripos($folder, 'bounce') !== false));
            $folders->attach(form_input_list_entry($folder, $selected, $label));
        }

        $fields = new Tempcode();
        $fields->attach(form_input_list(do_lang_tempcode('DIRECTORY'), new Tempcode(), 'box', $folders, null, true));

        $submit_name = do_lang_tempcode('PROCEED');
        $post_url = get_self_url();

        $post_url = build_url(array('page' => '_SELF', 'type' => 'bounce_filter_c'), '_SELF');
        return do_template('FORM_SCREEN', array('_GUID' => '69437ad3611c0ee55d09907985df8205', 'SKIP_WEBSTANDARDS' => true, 'HIDDEN' => build_keep_post_fields(), 'TITLE' => $this->title, 'TEXT' => '', 'FIELDS' => $fields, 'SUBMIT_ICON' => 'buttons__proceed', 'SUBMIT_NAME' => $submit_name, 'URL' => $post_url));
    }

    /**
     * The UI to confirm which subscribers to prune.
     *
     * @return Tempcode The UI
     */
    public function bounce_filter_c()
    {
        require_code('input_filter_2');
        modsecurity_workaround_enable();

        require_code('mail2');
        require_code('form_templates');

        $username = post_param_string('username');
        $password = post_param_string('password');
        $server = post_param_string('server');
        $port = post_param_integer('port');
        $box = post_param_string('box');

        $out = _find_mail_bounces($server, $port, $box, $username, $password, false);
        $num = count($out);

        $fields = '';//new Tempcode();

        $all_subscribers = array();
        $all_subscribers += collapse_2d_complexity('email', 'id', $GLOBALS['SITE_DB']->query_select('newsletter_subscribers', array('email', 'id')));
        if (get_forum_type() == 'cns') {
            $all_subscribers += collapse_2d_complexity('m_email_address', 'id', $GLOBALS['FORUM_DB']->query_select('f_members', array('m_email_address', 'id'), array('m_allow_emails_from_staff' => 1)));
        }

        $i = 0;
        foreach ($out as $email => $_details) {
            list($subject, $is_bounce) = $_details;

            if (array_key_exists($email, $all_subscribers)) {
                $tick = form_input_tick($email, $subject . '.', 'email_' . strval($num), $is_bounce, null, $email);
                $fields .= $tick->evaluate(); // XHTMLXHTML
                //$fields->attach($tick);

                $i++;
            }
        }

        if ($fields == '') {
            inform_exit(do_lang_tempcode('NO_ENTRIES'));
        }

        $submit_name = do_lang_tempcode('PROCEED');
        $post_url = get_self_url();

        $post_url = build_url(array('page' => '_SELF', 'type' => 'bounce_filter_d'), '_SELF');
        return do_template('FORM_SCREEN', array(
            '_GUID' => 'a517b87e2080204262d0bcf7fcebdf99',
            'SKIP_WEBSTANDARDS' => true,
            'HIDDEN' => build_keep_post_fields(),
            'TITLE' => $this->title,
            'TEXT' => do_lang_tempcode('BOUNCE_WHICH'),
            'FIELDS' => $fields,
            'SUBMIT_ICON' => 'buttons__proceed',
            'SUBMIT_NAME' => $submit_name,
            'URL' => $post_url,
            'MODSECURITY_WORKAROUND' => true,
        ));
    }

    /**
     * The actualiser to prune subscribers.
     *
     * @return Tempcode The UI
     */
    public function bounce_filter_d()
    {
        require_code('input_filter_2');
        modsecurity_workaround_enable();

        $title = get_screen_title('BOUNCE_FILTER');

        require_code('input_filter_2');
        rescue_shortened_post_request();

        $delete_sql = '';
        $delete_sql_members = '';

        foreach (array_keys($_POST) as $key) {
            if (substr($key, 0, 6) == 'email_') {
                if ($delete_sql != '') {
                    $delete_sql .= ' OR ';
                    $delete_sql_members .= ' OR ';
                }
                $delete_sql .= db_string_equal_to('email', post_param_string($key));
                $delete_sql_members .= db_string_equal_to('m_email_address', post_param_string($key));
            }
        }
        if ($delete_sql == '') {
            warn_exit(do_lang_tempcode('NOTHING_SELECTED'));
        }

        $query = 'DELETE FROM ' . get_table_prefix() . 'newsletter_subscribers WHERE ' . $delete_sql;
        $GLOBALS['SITE_DB']->query($query);

        $query = 'DELETE FROM ' . get_table_prefix() . 'newsletter_subscribe WHERE ' . $delete_sql;
        $GLOBALS['SITE_DB']->query($query);

        if (get_forum_type() == 'cns') {
            $query = 'UPDATE ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_members SET m_allow_emails_from_staff=0 WHERE ' . $delete_sql_members;
            $GLOBALS['FORUM_DB']->query($query);
        }

        return inform_screen($this->title, do_lang_tempcode('SUCCESS'));
    }

    /**
     * The UI to view subscribers on the newsletter.
     *
     * @return Tempcode The UI
     */
    public function view_subscribers()
    {
        $lang = choose_language($this->title);
        if (is_object($lang)) {
            return $lang;
        }

        if (php_function_allowed('set_time_limit')) {
            @set_time_limit(1000);
        }

        $id = either_param_string('id', null);
        $level = get_param_integer('level', null);

        require_lang('cns');

        require_code('crypt');

        // Select newsletter
        if (is_null($id)) {
            $fields = new Tempcode();
            require_code('form_templates');

            // Selection
            $newsletters = new Tempcode();
            $rows = $GLOBALS['SITE_DB']->query_select('newsletters', array('id', 'title'));
            foreach ($rows as $i => $newsletter) {
                $newsletters->attach(form_input_list_entry(strval($newsletter['id']), $i == 0, get_translated_text($newsletter['title'])));
            }
            if (get_forum_type() == 'cns') {
                $newsletters->attach(form_input_list_entry('-1', false, do_lang_tempcode('NEWSLETTER_CNS')));
                $groups = $GLOBALS['FORUM_DRIVER']->get_usergroup_list();
                foreach ($groups as $group_id => $group) {
                    if ($group_id != db_get_first_id()) {
                        $map = array();
                        $map['g' . strval($group_id)] = 1;
                        $_c = newsletter_who_send_to($map, $lang, 0, 0, false, '', true);
                        $c6 = $_c[6]['g' . strval($group_id)];
                        if ($c6 != 0) {
                            $newsletters->attach(form_input_list_entry('g' . strval($group_id), false, do_lang_tempcode('THIS_WITH', do_lang_tempcode('USERGROUP'), make_string_tempcode(escape_html($group)))));
                        }
                    }
                }
            }
            if ($newsletters->is_empty()) {
                inform_exit(do_lang_tempcode('NO_CATEGORIES'));
            }
            $fields->attach(form_input_list(do_lang_tempcode('NEWSLETTER'), '', 'id', $newsletters, null, true));

            // CSV option
            $fields->attach(form_input_tick(do_lang_tempcode('DOWNLOAD_AS_CSV'), do_lang_tempcode('DESCRIPTION_DOWNLOAD_AS_CSV'), 'csv', false));

            $submit_name = do_lang_tempcode('VIEW_SUBSCRIBERS');
            $post_url = get_self_url();

            $hidden = new Tempcode();
            $hidden->attach(form_input_hidden('lang', $lang));

            $prune_url = build_url(array('page' => '_SELF', 'type' => 'bounce_filter_a'), '_SELF');
            return do_template('FORM_SCREEN', array(
                '_GUID' => '0100ae6565474bca0669de1654b6efcf',
                'GET' => true,
                'SKIP_WEBSTANDARDS' => true,
                'HIDDEN' => $hidden,
                'TITLE' => $this->title,
                'TEXT' => do_lang_tempcode('NEWSLETTER_SUBSCRIBERS_FORM', escape_html($prune_url->evaluate())),
                'FIELDS' => $fields,
                'SUBMIT_ICON' => 'buttons__proceed',
                'SUBMIT_NAME' => $submit_name,
                'URL' => $post_url,
            ));
        }

        // Send to CSV file?
        $csv = either_param_integer('csv', 0);
        if ($csv == 1) {
            $filename = 'subscribers_' . $id . '.csv';

            header('Content-type: text/csv; charset=' . get_charset());
            header('Content-Disposition: attachment; filename="' . escape_header($filename, true) . '"');

            if (cms_srv('REQUEST_METHOD') == 'HEAD') {
                exit();
            }

            safe_ini_set('ocproducts.xss_detect', '0');
        }

        // Show subscribers
        $levels = is_null($level) ? (($id == '-1' || substr($id, 0, 1) == 'g') ? array(1) : ((get_option('interest_levels') == '1') ? array(1, 2, 3, 4) : array(4))) : array($level);
        $outs = array();
        foreach ($levels as $level) {
            $max = get_param_integer('max_' . (is_null($level) ? '' : strval($level)), 100);
            $start = get_param_integer('start_' . (is_null($level) ? '' : strval($level)), 0);

            $max_rows = 0;
            if (is_null($level)) { // implies all Conversr members
                $map[$id] = 1; // $id will be -1
                $_c = newsletter_who_send_to($map, $lang, 0, 0, true, '', true);
                if (isset($_c[6][$id])) {
                    $max_rows = $_c[6][$id];
                }
            } else { // implies normal newsletter / usergroup
                $map[$id] = $level; // We're requesting that we probe subscribers of $id on $level
                $_c = newsletter_who_send_to($map, $lang, 0, 0, true, '', true);
                if (isset($_c[6][$id])) {
                    $max_rows = $_c[6][$id];
                }
            }

            $num = 0;

            $start2 = 0;

            do {
                $map = array();
                if (is_null($level)) { // implies all Conversr members
                    $map[$id] = 1; // $id will be -1
                    $_c = newsletter_who_send_to($map, $lang, $start + $start2, $max, true, '', true);
                } else { // implies normal newsletter / usergroup
                    $map[$id] = $level; // We're requesting that we probe subscribers of $id on $level
                    $_c = newsletter_who_send_to($map, $lang, $start + $start2, $max, true, '', true);
                }
                $rows = $_c[7];

                if ($csv == 1) {
                    if ($start2 == 0) {
                        if (!is_null($level)) {
                            echo '"LEVEL ' . do_lang('NEWSLETTER_' . strval($level)) . '"' . "\n";
                        }
                        echo '"' . str_replace('"', '""', do_lang('EMAIL_ADDRESS')) . '",' . '"' . str_replace('"', '""', do_lang('FORENAME')) . '",' . '"' . str_replace('"', '""', do_lang('SURNAME')) . '",' . '"' . str_replace('"', '""', do_lang('NAME')) . '",' . '"' . str_replace('"', '""', do_lang('NEWSLETTER_SEND_ID')) . '",'/*Too slow with ratchet hash . '"' . str_replace('"', '""', do_lang('NEWSLETTER_HASH')) . '",'*/ . '"' . str_replace('"', '""', do_lang('PASSWORD_HASH')) . '",' . '"' . str_replace('"', '""', do_lang('SALT')) . '",' . '"' . str_replace('"', '""', do_lang('LANGUAGE')) . '",' . '"' . str_replace('"', '""', do_lang('CONFIRM_CODE')) . '",' . '"' . str_replace('"', '""', do_lang('JOIN_DATE')) . '",' . '"' . str_replace('"', '""', do_lang('SUBSCRIPTION_LEVEL')) . '"' . "\n";
                    }
                } else {
                    $out = '';
                }

                foreach ($rows as $r) {
                    $email = array_key_exists('email', $r) ? $r['email'] : $r['m_email_address'];
                    $forename = array_key_exists('n_forename', $r) ? $r['n_forename'] : '';
                    $surname = array_key_exists('n_surname', $r) ? $r['n_surname'] : '';
                    $name = array_key_exists('m_username', $r) ? $r['m_username'] : '';

                    $salt = array_key_exists('pass_salt', $r) ? $r['pass_salt'] : '';
                    $_language = array_key_exists('language', $r) ? $r['language'] : '';
                    $confirm_code = array_key_exists('confirm_code', $r) ? $r['confirm_code'] : 0;
                    $join_time = array_key_exists('join_time', $r) ? $r['join_time'] : time();

                    $send_id = (array_key_exists('m_username', $r) ? 'm' : 'n') . (array_key_exists('id', $r) ? strval($r['id']) : $email);
                    $hash = array_key_exists('the_password', $r) ? $r['the_password'] : '';
                    //$unsub = array_key_exists('the_password', $r) ? ratchet_hash($r['the_password'], 'xunsub') : '';

                    if ($csv == 1) {
                        echo '"' . str_replace('"', '""', $email) . '",' . '"' . str_replace('"', '""', $forename) . '",' . '"' . str_replace('"', '""', $surname) . '",' . '"' . str_replace('"', '""', $name) . '",' . '"' . str_replace('"', '""', $send_id) . '",'/* . '"' . str_replace('"', '""', $unsub) . '",'*/ . '"' . str_replace('"', '""', $hash) . '",' . '"' . str_replace('"', '""', $salt) . '",' . '"' . str_replace('"', '""', $_language) . '",' . '"' . str_replace('"', '""', strval($confirm_code)) . '",' . '"' . str_replace('"', '""', date('Y-m-d h:i:s', $join_time)) . '",' . '"' . strval($level) . '"' . "\n";
                    } else {
                        $tpl = do_template('NEWSLETTER_SUBSCRIBER', array('_GUID' => 'ca45867a23cbaa7c6788d3cd2ba2793c', 'EMAIL' => $email, 'FORENAME' => $forename, 'SURNAME' => $surname, 'NAME' => $name, 'NEWSLETTER_SEND_ID' => $send_id, 'NEWSLETTER_HASH' => $hash));
                        $out .= $tpl->evaluate();
                    }
                }

                $start2 += $max;
            } while (($csv == 1) && (array_key_exists(0, $rows)));

            if ((count($rows) == 0) && ($start2 == 0)) {
                if ($csv == 1) {
                    echo '"(' . do_lang('NONE') . ')"' . "\n";
                } else {
                }
            }

            $text = do_lang_tempcode('NEWSLETTER_PEOPLE_ON_LEVEL', ((is_numeric($level)) && (intval($level) > 0)) ? make_string_tempcode(escape_html(do_lang('NEWSLETTER_' . strval($level)))) : do_lang_tempcode('NA_EM'));

            if ($csv == 1) {
            } else {
                require_code('templates_pagination');
                $pagination = pagination(do_lang_tempcode('VIEW_NEWSLETTER_SUBSCRIBERS'), $start, 'start_' . (is_null($level) ? '' : strval($level)), $max, 'max_' . (is_null($level) ? '' : strval($level)), $max_rows);

                $outs[] = array('PAGINATION' => $pagination, 'SUB' => $out, 'TEXT' => $text);
            }
        }

        if ($csv == 1) {
            $GLOBALS['SCREEN_TEMPLATE_CALLED'] = '';
            exit();
        }

        // Work out stats of what domains are used
        $domains = array();
        $start = 0;
        do {
            if (substr($id, 0, 1) == 'g') {
                // TODO: Make this more accurate #3554
                if (strpos(get_db_type(), 'mysql') !== false) {
                    $rows = $GLOBALS['FORUM_DB']->query_select('f_members', array('DISTINCT m_email_address AS email', 'COUNT(*) as cnt'), array('m_allow_emails_from_staff' => 1, 'm_primary_group' => intval(substr($id, 1))), 'GROUP BY SUBSTRING_INDEX(m_email_address,\'@\',-1)'); // Far less PHP processing
                } else {
                    $rows = $GLOBALS['FORUM_DB']->query_select('f_members', array('DISTINCT m_email_address AS email'), array('m_allow_emails_from_staff' => 1, 'm_primary_group' => intval(substr($id, 1))), '', 500, $start);
                }
            } elseif ($id == '-1') {
                if (strpos(get_db_type(), 'mysql') !== false) {
                    $rows = $GLOBALS['FORUM_DB']->query_select('f_members', array('DISTINCT m_email_address AS email', 'COUNT(*) as cnt'), array('m_allow_emails_from_staff' => 1), 'GROUP BY SUBSTRING_INDEX(m_email_address,\'@\',-1)'); // Far less PHP processing
                } else {
                    $rows = $GLOBALS['FORUM_DB']->query_select('f_members', array('DISTINCT m_email_address AS email'), array('m_allow_emails_from_staff' => 1), '', 500, $start);
                }
            } else {
                $where = array('newsletter_id' => $id, 'code_confirm' => 0);
                if (get_option('interest_levels') == '1') {
                    $where['the_level'] = $level;
                }
                if (strpos(get_db_type(), 'mysql') !== false) {
                    $rows = $GLOBALS['SITE_DB']->query_select('newsletter_subscribe s JOIN ' . get_table_prefix() . 'newsletter_subscribers x ON s.email=x.email', array('DISTINCT s.email', 'COUNT(*) as cnt'), $where, 'GROUP BY SUBSTRING_INDEX(s.email,\'@\',-1)'); // Far less PHP processing
                } else {
                    $rows = $GLOBALS['SITE_DB']->query_select('newsletter_subscribe s JOIN ' . get_table_prefix() . 'newsletter_subscribers x ON s.email=x.email', array('DISTINCT s.email'), $where, '', 500, $start);
                }
            }
            foreach ($rows as $row) {
                $email = $row['email'];
                if (strpos($email, '@') === false) {
                    continue;
                }
                $domain = substr($email, strpos($email, '@') + 1);
                if (!is_string($domain)) {
                    continue;
                }
                $cnt = array_key_exists('cnt', $row) ? $row['cnt'] : 1;
                if (!array_key_exists($domain, $domains)) {
                    $domains[$domain] = 0;
                }
                $domains[$domain] += $cnt;
            }

            $start += 500;
        } while ((array_key_exists(0, $rows)) && (strpos(get_db_type(), 'mysql') === false));
        arsort($domains);
        foreach ($domains as $key => $val) {
            $domains[$key] = strval($val);
            if (count($domains) > 100) {
                if ($val == 1) {
                    unset($domains[$key]);
                }
            }
        }

        $tpl = do_template('NEWSLETTER_SUBSCRIBERS_SCREEN', array('_GUID' => '52e5d97d451b622d59f87f021a5b8f01', 'DOMAINS' => $domains, 'SUBSCRIBERS' => $outs, 'TITLE' => $this->title));

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

    /**
     * The UI to create an automated what's new newsletter. Select cut-off time and other settings.
     *
     * @return Tempcode The UI
     */
    public function whatsnew_1()
    {
        $lang = choose_language($this->title);
        if (is_object($lang)) {
            return $lang;
        }

        require_code('form_templates');

        $fields = new Tempcode();

        $_cutoff_time = get_value('newsletter_whatsnew');
        $cutoff_time = is_null($_cutoff_time) ? null : intval($_cutoff_time);
        if (is_null($cutoff_time)) {
            $cutoff_time = time() - 60 * 60 * 24 * 365 * 3;
        }
        $fields->attach(form_input_date(do_lang_tempcode('CUTOFF_DATE'), do_lang_tempcode('DESCRIPTION_CUTOFF_DATE'), 'cutoff', true, false, true, $cutoff_time, -3));

        $fields->attach(form_input_tick(do_lang_tempcode('EMBED_FULL_ARTICLES'), do_lang_tempcode('DESCRIPTION_EMBED_FULL_ARTICLES'), 'in_full', post_param_integer('in_full', 0) == 1));

        $hidden = new Tempcode();

        $text = new Tempcode();

        if (cron_installed()) {
            $periodic_options = new Tempcode();

            $current_periodic_newsletters = $GLOBALS['SITE_DB']->query_select('newsletter_periodic', array('*'));
            if (count($current_periodic_newsletters) == 0) {
                $extra_help = do_lang('PERIODIC_NEWSLETTER_EMPTY');
                $periodic_choice_name = do_lang('PERIODIC_CREATE');
                $periodic_choice_help = do_lang('PERIODIC_CREATE_HELP');
                $periodic_options->attach(form_input_list_entry('no_change', true, do_lang('DONT_MAKE_PERIODIC_NEWSLETTER'), false, false));
                $periodic_options->attach(form_input_list_entry('make_periodic', false, do_lang('MAKE_PERIODIC_NEWSLETTER'), false, false));
            } else {
                $extra_help = do_lang('PERIODIC_NEWSLETTER_EXISTS');
                $periodic_choice_name = do_lang('PERIODIC_REPLACE');
                $periodic_choice_help = do_lang('PERIODIC_REPLACE_HELP');
                $periodic_options->attach(form_input_list_entry('no_change', true, do_lang('LEAVE_PERIODIC_NEWSLETTER'), false, false));
                $periodic_options->attach(form_input_list_entry('make_periodic', false, do_lang('MAKE_PERIODIC_NEWSLETTER'), false, false));
                foreach ($current_periodic_newsletters as $current_periodic_newsletter) {
                    $periodic_options->attach(form_input_list_entry('remove_existing_' . strval($current_periodic_newsletter['id']), false, do_lang('REMOVE_PERIODIC', $current_periodic_newsletter['np_subject'], strval($current_periodic_newsletter['id'])), false, false));
                    $periodic_options->attach(form_input_list_entry('replace_existing_' . strval($current_periodic_newsletter['id']), false, do_lang('REPLACE_PERIODIC', $current_periodic_newsletter['np_subject'], strval($current_periodic_newsletter['id'])), false, false));
                }
            }
            $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array(
                '_GUID' => '9360e476c6fd8ed95176d05b866ee553',
                'TITLE' => do_lang('PERIODIC_NEWSLETTER_SETTINGS'),
                'HELP' => do_lang('PERIODIC_NEWSLETTER_HELP', $extra_help),
            )));

            $fields->attach(form_input_list($periodic_choice_name, $periodic_choice_help, 'periodic_choice', $periodic_options, null, false, false));

            if (count($current_periodic_newsletters) > 0) {
                $text = do_lang_tempcode('PERIODIC_NEWSLETTER_AMEND');
            }
        }

        return do_template('FORM_SCREEN', array(
            '_GUID' => 'ce1af424e01219c8dee2a7867c1647ef',
            'SKIP_WEBSTANDARDS' => true,
            'HIDDEN' => $hidden,
            'TITLE' => $this->title,
            'TEXT' => $text,
            'FIELDS' => $fields,
            'SUBMIT_ICON' => 'buttons__proceed',
            'SUBMIT_NAME' => do_lang_tempcode('NEXT'),
            'URL' => get_self_url(false, false, array('lang' => $lang, 'type' => 'whatsnew_2')),
        ));
    }

    /**
     * The UI to create an automated what's new newsletter. Select content categories.
     *
     * @return Tempcode The UI
     */
    public function whatsnew_2()
    {
        require_code('form_templates');
        require_code('global4');

        if (php_function_allowed('set_time_limit')) {
            @set_time_limit(180);
        }
        send_http_output_ping();
        disable_php_memory_limit();

        $fields = new Tempcode();

        $lang = choose_language($this->title);

        $cutoff_time = post_param_date('cutoff');
        if ($cutoff_time === null) {
            warn_exit(do_lang_tempcode('NO_PARAMETER_SENT', escape_html('cutoff')));
        }

        $chosen_categories = '';

        $_hooks = find_all_hooks('modules', 'admin_newsletter');
        foreach (array_keys($_hooks) as $hook) {
            require_code('hooks/modules/admin_newsletter/' . filter_naughty_harsh($hook));
            $object = object_factory('Hook_whatsnew_' . filter_naughty_harsh($hook), true);
            if (is_null($object)) {
                continue;
            }

            $done = false;
            if (method_exists($object, 'choose_categories')) {
                list($cats, $_title) = $object->choose_categories($cutoff_time);
                if (is_object($cats)) {
                    $cats = $cats->evaluate($lang);
                }
                $matches = array();
                $num_matches = preg_match_all('#<option [^>]*value="([^"]*)"[^>]*>([^<]*)</option>#', $cats, $matches);
                if ($num_matches < 1500) { /*reasonable limit*/
                    for ($i = 0; $i < $num_matches; $i++) {
                        $hook_result = $object->run($cutoff_time, $lang, $matches[1][$i]);
                        if ($hook_result == array()) {
                            continue;
                        }
                        list($hook_content, $_title) = $hook_result;
                        if (!$hook_content->is_empty()) {
                            $decoded = @html_entity_decode($matches[2][$i], ENT_QUOTES, get_charset());
                            $chosen_categories .= $_title . ': ' . trim($decoded) . ' [' . $hook . '/' . $matches[1][$i] . "]\n";
                        }
                    }
                    $done = true;
                }
            }
            if (!$done) {
                $new = $object->run($cutoff_time, $lang, '');
                if ($new != array()) {
                    list($hook_content, $_title) = $new;
                    if (!$hook_content->is_empty()) {
                        $chosen_categories .= $_title . ' [' . $hook . "]\n";
                    }
                }
            }
        }
        $fields->attach(form_input_huge(do_lang_tempcode('WHATSNEW_CATEGORIES_SELECT'), do_lang('DESCRIPTION_WHATSNEW_CATEGORIES_SELECT'), 'chosen_categories', $chosen_categories, true));

        $hidden = new Tempcode();
        $hidden->attach(build_keep_post_fields());

        $text = do_lang_tempcode('SELECT_CATEGORIES_WANTED');

        return do_template('FORM_SCREEN', array(
            '_GUID' => 'bacc372b7338d8e1103facc05ae4598f',
            'SKIP_WEBSTANDARDS' => true,
            'HIDDEN' => $hidden,
            'TITLE' => $this->title,
            'TEXT' => $text,
            'FIELDS' => $fields,
            'SUBMIT_ICON' => 'buttons__proceed',
            'SUBMIT_NAME' => do_lang_tempcode('NEXT'),
            'URL' => get_self_url(false, false, array('lang' => $lang, 'type' => 'whatsnew_3')),
        ));
    }

    /**
     * The UI to create an automated what's new newsletter. Pipe through to the newsletter screen.
     *
     * @return Tempcode The UI
     */
    public function whatsnew_3()
    {
        require_code('global4');

        // Handle requested periodic "What's new" newsletter maintenance
        // =============================================================

        $matches = array();

        // Confirm screen for removal
        if (preg_match('#^remove\_existing\_(\d+)$#', post_param_string('periodic_choice', ''), $matches) != 0) {
            $hidden = new Tempcode();
            $hidden->attach(form_input_hidden('periodic_choice', 'periodic_remove_confirmed_' . $matches[1]));
            return do_template('PERIODIC_NEWSLETTER_REMOVE', array(
                '_GUID' => '4fe61ba93e2a05ae9f987e462687d6d5',
                'TITLE' => get_screen_title('REMOVE_PERIODIC_NEWSLETTER'),
                'URL' => get_self_url(),
                'HIDDEN' => $hidden,
            ));
        }
        // Actualiser for removal
        if (preg_match('#^periodic\_remove\_confirmed\_(\d+)$#', post_param_string('periodic_choice', ''), $matches) != 0) {
            delete_periodic_newsletter(intval($matches[1]));

            // We redirect back to the admin_newsletter main page
            $url = build_url(array('page' => 'admin_newsletter', 'type' => 'browse', 'redirected' => '1'), get_module_zone('admin_newsletter'));
            return redirect_screen(do_lang('PERIODIC_REMOVED'), $url, do_lang('PERIODIC_REMOVED_TEXT'));
        }

        // Build the "What's new" newsletter and proceed to editing
        // ========================================================

        $lang = choose_language($this->title);

        $cutoff_time = post_param_date('cutoff');
        $in_full = post_param_integer('in_full', 0);
        $chosen_categories = post_param_string('chosen_categories');
        $message = $this->_generate_whatsnew_comcode($chosen_categories, $in_full, $lang, $cutoff_time);

        return $this->send_gui($message->evaluate());
    }

    /**
     * Generate Comcode for a what's new newsletter.
     *
     * @param  LONG_TEXT $chosen_categories Category selection
     * @param  BINARY $in_full Whether to show artices in full (as opposed to summaries)
     * @param  LANGUAGE_NAME $lang Language to send in
     * @param  TIME $cutoff_time When to cut off content from
     * @return Tempcode The Comcode, in template form
     */
    public function _generate_whatsnew_comcode($chosen_categories, $in_full, $lang, $cutoff_time)
    {
        require_code('global4');

        $_hooks = find_all_hooks('modules', 'admin_newsletter');

        // Generate Comcode for content selected, drawing on hooks
        $automatic = array();
        $i = 0;
        $catarr = explode("\n", $chosen_categories);
        foreach (array_keys($_hooks) as $hook) {
            require_code('hooks/modules/admin_newsletter/' . filter_naughty_harsh($hook));
            $object = object_factory('Hook_whatsnew_' . filter_naughty_harsh($hook), true);
            if (is_null($object)) {
                continue;
            }
            $found_one_match = false;
            $last_find_id = mixed();
            $last_cat_id = null;
            $filter = '';
            foreach ($catarr as $find_id => $line) {
                $matches = array();
                if (preg_match('#\[' . preg_quote($hook, '#') . '/(.*)\]#', $line, $matches) != 0) {
                    $found_one_match = true;

                    if ((!is_null($last_find_id)) && (($find_id != $last_find_id + 1)/* || ($last_cat_id>intval($matches[1]))*/)) {
                        $last_cat_id = intval($matches[1]);

                        $temp = $object->run(intval($cutoff_time), $lang, $filter);
                        if ((is_null($temp)) || (count($temp) == 0)) {
                            continue;
                        }
                        if (!$temp[0]->is_empty()) {
                            $tmp = do_template('NEWSLETTER_WHATSNEW_SECTION_FCOMCODE', array(
                                '_GUID' => 'bd228cdeafacfffac2d8d98d5f2da565',
                                'I' => strval($i + 1),
                                'TITLE' => $temp[1],
                                'CONTENT' => $temp[0],
                                'THUMBNAIL' => array_key_exists(2, $temp) ? $temp[2] : ''
                            ), null, false, null, '.txt', 'text');
                            $automatic[$last_find_id] = $tmp->evaluate($lang); /*FUDGE*/
                            $i++;
                        }

                        $filter = $matches[1];
                    } else {
                        if ($filter != '') {
                            $filter .= ',';
                        }
                        $filter .= $matches[1];
                    }

                    $last_find_id = $find_id;
                }
            }
            if (!$found_one_match) {
                $found = false;
                foreach ($catarr as $find_id => $line) {
                    if (strpos($line, '[' . $hook . ']') !== false) {
                        $found = true;
                        break;
                    }
                }
                if (!$found) {
                    continue;
                }

                $temp = $object->run(intval($cutoff_time), $lang, $filter, $in_full);
                if ((is_null($temp)) || (count($temp) == 0)) {
                    continue;
                }
                if (!$temp[0]->is_empty()) {
                    $tmp = do_template('NEWSLETTER_WHATSNEW_SECTION_FCOMCODE', array(
                        '_GUID' => '64c8870e7c75354c07b2e94f299cd38c',
                        'I' => strval($i + 1),
                        'TITLE' => $temp[1],
                        'CONTENT' => $temp[0]
                    ), null, false, null, '.txt', 'text');
                    $automatic[$find_id] = $tmp->evaluate($lang); /*FUDGE*/
                    $i++;
                }
            } elseif ($filter != '') {
                $temp = $object->run(intval($cutoff_time), $lang, $filter, $in_full);
                if ((is_null($temp)) || (count($temp) == 0)) {
                    continue;
                }
                if (!$temp[0]->is_empty()) {
                    $tmp = do_template('NEWSLETTER_WHATSNEW_SECTION_FCOMCODE', array(
                        '_GUID' => '8d1e7f448d11853b675a0949b8a0c2c9',
                        'I' => strval($i + 1),
                        'TITLE' => $temp[1],
                        'CONTENT' => $temp[0]
                    ), null, false, null, '.txt', 'text');
                    $automatic[$last_find_id] = $tmp->evaluate($lang); /*FUDGE*/
                    $i++;
                }
            }
        }
        ksort($automatic);
        $_automatic = '';
        foreach ($automatic as $tp) {
            $_automatic .= $tp;
        }
        $completed = do_template('NEWSLETTER_WHATSNEW_FCOMCODE', array('_GUID' => '20f6adc244b04d9e5206682ec4e0cc0f', 'CONTENT' => $_automatic), null, false, null, '.txt', 'text');

        $completed = do_template('NEWSLETTER_DEFAULT_FCOMCODE', array('_GUID' => '53c02947915806e519fe14c318813f46', 'CONTENT' => $completed, 'LANG' => $lang, 'SUBJECT' => ''), null, false, null, '.txt', 'text');

        return $completed;
    }

    /**
     * The UI to send a newsletter.
     *
     * @param  LONG_TEXT $_existing Default newsletter to put in
     * @return Tempcode The UI
     */
    public function send_gui($_existing = '')
    {
        $blocked = newsletter_block_list();
        if (count($blocked) != 0) {
            attach_message(do_lang_tempcode('BLOCK_LIST_IN_PLACE', escape_html(number_format(count($blocked)))), 'notice');
        }

        // If this is a periodic newsletter, we make some changes to the regular
        // language strings.
        $periodic_action_raw = post_param_string('periodic_choice', '');
        $periodic_subject = '';
        $defaults = mixed();
        switch (preg_replace('#\_\d+$#', '', $periodic_action_raw)) {
            case 'remove_existing':
                // Remove whatever is already set. We don't need any changes for
                // this, but we do need a hidden form field.
                $periodic_action = 'remove';
                break;
            case 'replace_existing':
                // Make the current newsletter periodic. This requires language
                // fiddling.
                $periodic_action = 'replace';
                $periodic_subject = do_lang('PERIODIC_SUBJECT_HELP');
                $periodic_id = intval(preg_replace('#^[^\d]+#', '', $periodic_action_raw));
                $_defaults = $GLOBALS['SITE_DB']->query_select('newsletter_periodic', array('*'), array('id' => $periodic_id), '', 1);
                if (!array_key_exists(0, $_defaults)) {
                    warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
                }
                $defaults = $_defaults[0];
                break;
            case 'make_periodic':
                // Make the current newsletter periodic. This requires language
                // fiddling.
                $periodic_action = 'make';
                $periodic_subject = do_lang('PERIODIC_SUBJECT_HELP');
                break;
            case 'no_change':
            default:
                // The default action is to leave the current settings as-is.
                $periodic_action = 'none';
                break;
        }

        $lang = choose_language($this->title);
        if (is_object($lang)) {
            return $lang;
        }

        $post_url = build_url(array('page' => '_SELF', 'type' => 'confirm', 'old_type' => get_param_string('type', '')), '_SELF');

        $submit_name = do_lang_tempcode('PREVIEW');

        $hidden = new Tempcode();
        $hidden->attach(form_input_hidden('lang', $lang));

        // Build up form...
        // ================

        $fields = new Tempcode();
        require_code('form_templates');

        $default_subject = get_option('newsletter_title');
        if (!is_null($defaults)) {
            $default_subject = $defaults['np_subject'];
        }
        if ($periodic_action != 'make' && $periodic_action != 'replace') {
            $default_subject .= ' - ' . get_timezoned_date(time(), false, false, false, true);
        }
        $default_subject = post_param_string('subject', $default_subject);

        $fields->attach(form_input_line_comcode(do_lang_tempcode('SUBJECT'), do_lang_tempcode('NEWSLETTER_DESCRIPTION_TITLE', escape_html($periodic_subject)), 'subject', $default_subject, true));

        $in_full = post_param_integer('in_full', 0);
        $chosen_categories = post_param_string('chosen_categories', '');

        // Newsletter message (complex, as will depend if an automatic periodical being made, meaning no message defined now)
        $comcode_given = ($_existing != '') && (strpos($_existing, '<html') !== false);
        $_existing = post_param_string('message', $_existing);
        if ($_existing == '') {
            $from_news = get_param_integer('from_news', -1);
            if (($from_news != -1) && (addon_installed('news'))) {
                $rows = $GLOBALS['SITE_DB']->query_select('news', array('*'), array('id' => $from_news), 'ORDER BY id DESC', 1);
                if (!array_key_exists(0, $rows)) {
                    require_lang('news');
                    return warn_screen(get_screen_title('NEWS'), do_lang_tempcode('MISSING_RESOURCE'));
                }
                $myrow = $rows[0];

                $_existing = get_translated_text($myrow['news_article'], null, $lang);
                if ($_existing == '') {
                    $_existing = get_translated_text($myrow['news'], null, $lang);
                }
            }
            $existing = do_template('NEWSLETTER_DEFAULT_FCOMCODE', array('_GUID' => '53c02947915806e519fe14c318813f42', 'CONTENT' => $_existing, 'LANG' => $lang, 'SUBJECT' => $default_subject), null, false, null, '.txt', 'text');
        } else {
            $default = do_template('NEWSLETTER_DEFAULT_FCOMCODE', array('_GUID' => '53c02947915806e519fe14c318813f44', 'CONTENT' => $_existing, 'LANG' => $lang, 'SUBJECT' => $default_subject), null, false, null, '.txt', 'text');
            if (strpos($default->evaluate(), '<html') !== false && strpos($_existing, '<html') === false) { // Our template contains HTML, so we need to pull in that HTML to the edit field (it's a full design email, not a simple encapsulation)
                if ($comcode_given) {
                    $default = do_template('NEWSLETTER_DEFAULT_FCOMCODE', array('_GUID' => '8c38d2cf2e60fb06410be34b34da7997', 'CONTENT' => comcode_to_tempcode($_existing), 'LANG' => $lang, 'SUBJECT' => $default_subject), null, false, null, '.txt', 'text');
                }
                $existing = $default;
            } else {
                $existing = make_string_tempcode($_existing);
            }
        }
        if ($periodic_action == 'make' || $periodic_action == 'replace') {
            // We are making a periodic newsletter. This means we need to pass
            // through the chosen categories - add extra fields to the form
            if (!is_null($defaults)) {
                $chosen_categories = $defaults['np_message'];
                $in_full = $defaults['np_in_full'];

                $fields->attach(form_input_tick(do_lang_tempcode('EMBED_FULL_ARTICLES'), do_lang_tempcode('DESCRIPTION_EMBED_FULL_ARTICLES'), 'in_full', $in_full == 1));
                $fields->attach(form_input_huge(do_lang_tempcode('WHATSNEW_CATEGORIES_SELECT'), do_lang('DESCRIPTION_WHATSNEW_CATEGORIES_SELECT'), 'chosen_categories', $chosen_categories, true));
            } else {
                $hidden->attach(form_input_hidden('chosen_categories', $chosen_categories));
                $hidden->attach(form_input_hidden('in_full', strval($in_full)));
            }
            if (!is_null(post_param_string('cutoff', null))) {
                $hidden->attach(form_input_hidden('cutoff', post_param_string('cutoff')));
                $hidden->attach(form_input_hidden('cutoff_time', post_param_string('cutoff_time')));
            } else {
                $hidden->attach(form_input_hidden('cutoff_day', post_param_string('cutoff_day')));
                $hidden->attach(form_input_hidden('cutoff_month', post_param_string('cutoff_month')));
                $hidden->attach(form_input_hidden('cutoff_year', post_param_string('cutoff_year')));
                $hidden->attach(form_input_hidden('cutoff_hour', post_param_string('cutoff_hour')));
                $hidden->attach(form_input_hidden('cutoff_minute', post_param_string('cutoff_minute')));
            }

            $hidden->attach(form_input_hidden('message', $existing->evaluate()));
        } else {
            $hidden->attach(form_input_hidden('in_full', strval($in_full)));

            if (strpos($existing->evaluate(), '<html') === false) {
                $fields->attach(form_input_huge_comcode(do_lang_tempcode('MESSAGE'), do_lang_tempcode('DESCRIPTION_MESSAGE_NEWSLETTER'), 'message', $existing->evaluate(), true));
            } else {
                $fields->attach(form_input_huge(do_lang_tempcode('MESSAGE'), do_lang_tempcode('DESCRIPTION_MESSAGE_NEWSLETTER'), 'message', $existing->evaluate(), true));
            }
        }

        // Some general details of how to send
        if ((addon_installed('calendar')) && ($periodic_action == 'none') && (cron_installed())) {
            $fields->attach(form_input_date__scheduler(do_lang_tempcode('DEFER_TIME'), do_lang_tempcode('DESCRIPTION_DEFER_TIME'), 'schedule', false, true, true));
        }
        $from_email = post_param_string('from_email', get_option('staff_address'));
        if (!is_null($defaults)) {
            $from_email = post_param_string('from_email', $defaults['np_from_email']);
        }
        $fields->attach(form_input_email(do_lang_tempcode('FROM_EMAIL'), do_lang_tempcode('DESCRIPTION_NEWSLETTER_FROM_EMAIL'), 'from_email', $from_email, true));
        $from_name = post_param_string('from_name', get_site_name());
        if (!is_null($defaults)) {
            $from_name = post_param_string('from_name', $defaults['np_from_name']);
        }
        $fields->attach(form_input_line(do_lang_tempcode('FROM_NAME'), do_lang_tempcode('DESCRIPTION_NEWSLETTER_FROM_NAME'), 'from_name', $from_name, true));
        $_html_only = post_param_integer('html_only', null);
        if (is_null($_html_only)) {
            $html_only = (strpos($existing->evaluate(), '<html') !== false);
            if (!is_null($defaults)) {
                $html_only = $defaults['np_html_only'];
            }
        } else {
            $html_only = ($_html_only == 1);
        }
        if (get_option('dual_format_newsletters') == '0') {
            $hidden->attach(form_input_hidden('html_only', '1'));
        } else {
            $fields->attach(form_input_tick(do_lang_tempcode('HTML_ONLY'), do_lang_tempcode('DESCRIPTION_HTML_ONLY'), 'html_only', $html_only));
        }
        $l = new Tempcode();
        $priority = post_param_integer('priority', 3);
        if (!is_null($defaults)) {
            $priority = post_param_integer('priority', $defaults['np_priority']);
        }
        for ($i = 1; $i <= 5; $i++) {
            $l->attach(form_input_list_entry(strval($i), $i == $priority, do_lang_tempcode('PRIORITY_' . strval($i))));
        }
        $fields->attach(form_input_list(do_lang_tempcode('PRIORITY'), do_lang_tempcode('DESCRIPTION_NEWSLETTER_PRIORITY'), 'priority', $l));

        // Where to send to
        $csv_data = post_param_string('csv_data', null);
        $send_to_help = mixed();
        if (is_null($csv_data)) { // Maybe discern it from passed parameters from search module
            $_csv_data = array();
            $_csv_data[] = array(do_lang('EMAIL_ADDRESS'), do_lang('NAME'), do_lang('NEWSLETTER_SEND_ID'));
            foreach (array_keys($_POST) as $post_key) {
                if (!is_string($post_key)) {
                    $post_key = strval($post_key);
                }

                $matches = array();
                if ((preg_match('#^result\_\_member_(\d+)$#', $post_key, $matches) != 0) && (post_param_integer($post_key, 0) == 1)) {
                    $member_id = intval($matches[1]);
                    $_csv_data[] = array($GLOBALS['FORUM_DRIVER']->get_member_email_address($member_id), $GLOBALS['FORUM_DRIVER']->get_username($member_id), 'm' . strval($member_id));
                }
            }
            if (count($_csv_data) > 1) {
                $csv_data = serialize($_csv_data);
            }
        }
        if (!is_null($csv_data)) {
            $hidden->attach(form_input_hidden('csv_data', $csv_data));
            secure_serialized_data($csv_data, array());
            $_csv_data = cms_unserialize($csv_data);
            $num_csv_data = count($_csv_data) - 1;
            $send_to_help = do_lang_tempcode('SOME_NEWSLETTER_TARGETS_KNOWN', escape_html(integer_format($num_csv_data)));
        }
        $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => '7e1c75fef01054164abfa72f55e5ba86', 'TITLE' => do_lang_tempcode('CHOOSE_SEND_TO'), 'HELP' => $send_to_help)));
        $newsletters = $GLOBALS['SITE_DB']->query_select('newsletters', array('*'));
        foreach ($newsletters as $newsletter) {
            $level = post_param_integer(strval($newsletter['id']), post_param_integer('level', -1));

            $c4 = $this->_count_level($newsletter['id'], 4, $lang);
            $c3 = $this->_count_level($newsletter['id'], 3, $lang);
            $c2 = $this->_count_level($newsletter['id'], 2, $lang);
            $c1 = $this->_count_level($newsletter['id'], 1, $lang);
            //if ($c1!=0) {  Actually, this just confuses people if we don't show it
            $newsletter_title = get_translated_text($newsletter['title']);
            $newsletter_description = get_translated_text($newsletter['description']);

            if (($c1 == $c2) && ($c1 == $c3) && ($c1 == $c4)) {
                $fields->attach(form_input_tick(do_lang_tempcode('NEWSLETTER_PREFIX', escape_html($newsletter_title)), do_lang_tempcode('DESCRIPTION_NOSUBSCRIPTION_LEVEL', escape_html(integer_format($c4)), escape_html($newsletter_description)), strval($newsletter['id']), $level >= 1, null, '4'));
            } else {
                $l = new Tempcode();
                $l->attach(form_input_list_entry('0', $level == 0, do_lang_tempcode('NNR', do_lang_tempcode('NEWSLETTER_0_ALT'), do_lang_tempcode('NUM_READERS', escape_html(integer_format(0))))));
                $l->attach(form_input_list_entry('1', $level == 1, do_lang_tempcode('NNR', do_lang_tempcode('NEWSLETTER_1'), do_lang_tempcode('NUM_READERS', escape_html(integer_format($c1))))));
                $l->attach(form_input_list_entry('2', $level == 2, do_lang_tempcode('NNR', do_lang_tempcode('NEWSLETTER_2'), do_lang_tempcode('NUM_READERS', escape_html(integer_format($c2))))));
                $l->attach(form_input_list_entry('3', $level == 3, do_lang_tempcode('NNR', do_lang_tempcode('NEWSLETTER_3'), do_lang_tempcode('NUM_READERS', escape_html(integer_format($c3))))));
                $l->attach(form_input_list_entry('4', $level == 4, do_lang_tempcode('NNR', do_lang_tempcode('NEWSLETTER_4'), do_lang_tempcode('NUM_READERS', escape_html(integer_format($c4))))));

                $fields->attach(form_input_list(do_lang_tempcode('SUBSCRIPTION_LEVEL_FOR', escape_html($newsletter_title)), do_lang_tempcode('DESCRIPTION_SUBSCRIPTION_LEVEL', escape_html($newsletter_description)), strval($newsletter['id']), $l));
            }
            //}
        }
        if (get_forum_type() == 'cns') {
            $c5 = $this->_count_level(-1, 5, $lang);
            $fields->attach(form_input_tick(do_lang_tempcode('NEWSLETTER_CNS'), do_lang_tempcode('NUM_READERS', escape_html(integer_format($c5))), '-1', false));
            $groups = $GLOBALS['FORUM_DRIVER']->get_usergroup_list();
            foreach ($groups as $group_id => $group) {
                if ($group_id != db_get_first_id()) {
                    $map = array();
                    $map['g' . strval($group_id)] = 1;
                    $_c = newsletter_who_send_to($map, $lang, 0, 0, false, '', true);
                    $c6 = $_c[6]['g' . strval($group_id)];
                    if ($c6 != 0) {
                        $fields->attach(form_input_tick(do_lang_tempcode('THIS_WITH', do_lang_tempcode('USERGROUP'), make_string_tempcode(escape_html($group))), do_lang_tempcode('NUM_READERS', escape_html(integer_format($c6))), 'g' . strval($group_id), post_param_integer('g' . strval($group_id), 0) == 1));
                    }
                }
            }
        }
        if (is_null($csv_data)) {
            $fields->attach(form_input_upload(do_lang_tempcode('UPLOAD'), do_lang_tempcode('DESCRIPTION_UPLOAD_CSV'), 'file', false, null, null, true, 'csv,txt'));
        }

        handle_max_file_size($hidden);

        // Which newsletter template?
        $template_choices = new Tempcode();
        $dh = opendir(get_custom_file_base() . '/themes/default/templates_custom');
        while (($f = readdir($dh)) !== false) {
            if (preg_match('#^MAIL.*\.tpl$#', $f) != 0) {
                $tpl = basename($f, '.tpl');
                $template_choices->attach(form_input_list_entry($tpl, post_param_string('template', 'MAIL') == $tpl, $tpl));
            }
        }
        if (!file_exists(get_custom_file_base() . '/themes/default/templates_custom/MAIL.tpl')) {
            $template_choices->attach(form_input_list_entry('MAIL', true, 'MAIL'));
        }
        closedir($dh);
        $fields->attach(form_input_list(do_lang_tempcode('NEWSLETTER_TEMPLATE'), do_lang_tempcode('DESCRIPTION_NEWSLETTER_TEMPLATE'), 'template', $template_choices, null, false, true));

        // If we're making a periodic newsletter then we need to know when it
        // should be sent
        if ($periodic_action == 'make' || $periodic_action == 'replace') {
            $hidden->attach(form_input_hidden('make_periodic', '1'));
            $hidden->attach(form_input_hidden('periodic_choice', post_param_string('periodic_choice')));
            $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => '1e6e0f900f85aa4ed54318801a1810bb', 'TITLE' => do_lang('PERIODIC_WHEN'), 'HELP' => do_lang('PERIODIC_WHEN_HELP'))));

            // The choices are given as radio buttons: weekly or bi-weekly or monthly?
            // In the labels for these radio buttons, we put a dropdown for day of
            // the week and day of the month.

            $frequency = post_param_string('periodic_when', 'weekly');
            if (!is_null($defaults)) {
                $frequency = post_param_string('periodic_when', $defaults['np_frequency']);
            }
            $current_day_weekly = post_param_integer('periodic_weekly', 5);
            if (!is_null($defaults)) {
                $current_day_weekly = post_param_integer('periodic_weekly', $defaults['np_day']);
            }
            $current_day_biweekly = post_param_integer('periodic_biweekly', 5);
            if (!is_null($defaults)) {
                $current_day_biweekly = post_param_integer('periodic_biweekly', $defaults['np_day']);
            }
            $current_day_of_month = post_param_integer('periodic_monthly', 1);
            if (!is_null($defaults)) {
                $current_day_of_month = post_param_integer('periodic_monthly', $defaults['np_day']);
            }

            $radios = new Tempcode();

            $week_days_weekly = new Tempcode();
            $week_days_biweekly = new Tempcode();
            require_lang('dates');
            $week_days = array(1 => do_lang('MONDAY'), 2 => do_lang('TUESDAY'), 3 => do_lang('WEDNESDAY'), 4 => do_lang('THURSDAY'), 5 => do_lang('FRIDAY'), 6 => do_lang('SATURDAY'), 7 => do_lang('SUNDAY'));
            foreach ($week_days as $i => $this_day) {
                $week_days_weekly->attach(form_input_list_entry(strval($i), ($i == $current_day_weekly), $this_day, false, false));
                $week_days_biweekly->attach(form_input_list_entry(strval($i), ($i == $current_day_biweekly), $this_day, false, false));
            }

            $weekly_desc = new Tempcode();
            $weekly_desc->attach(do_lang('PERIODIC_WEEKLY_ON'));
            $weekly_desc->attach(do_template('FORM_SCREEN_INPUT_LIST', array('_GUID' => 'b0c43b5f6883be80af5911a587fc85bf', 'TABINDEX' => strval(get_form_field_tabindex(null)), 'REQUIRED' => '0', 'NAME' => 'periodic_weekday_weekly', 'CONTENT' => $week_days_weekly, 'INLINE_LIST' => '0', 'SIZE' => '9')));
            $radios->attach(form_input_radio_entry('periodic_when', 'weekly', $frequency == 'weekly', $weekly_desc, null, ''));

            $biweekly_desc = new Tempcode();
            $biweekly_desc->attach(do_lang('PERIODIC_BIWEEKLY_ON'));
            $biweekly_desc->attach(do_template('FORM_SCREEN_INPUT_LIST', array('_GUID' => '533afb6cdf1da813dd55ae694b962151', 'TABINDEX' => strval(get_form_field_tabindex(null)), 'REQUIRED' => '0', 'NAME' => 'periodic_weekday_biweekly', 'CONTENT' => $week_days_biweekly, 'INLINE_LIST' => '0', 'SIZE' => '9')));
            $radios->attach(form_input_radio_entry('periodic_when', 'biweekly', $frequency == 'biweekly', $biweekly_desc, null, ''));

            $month_days = new Tempcode();
            foreach (range(1, 28) as $this_day) {
                $suffix = gmdate('S', gmmktime(0, 0, 0, 1, $this_day, 1990));
                $month_days->attach(form_input_list_entry(strval($this_day), ($this_day == 1), strval($this_day) . $suffix, $current_day_of_month == $this_day));
            }
            $monthly_desc = new Tempcode();
            $monthly_desc->attach(do_lang('PERIODIC_MONTHLY_ON'));
            $monthly_desc->attach(do_template('FORM_SCREEN_INPUT_LIST', array('_GUID' => '352012c3153342f5a954fcfa16c5503b', 'TABINDEX' => strval(get_form_field_tabindex(null)), 'REQUIRED' => '0', 'NAME' => 'periodic_monthly', 'CONTENT' => $month_days, 'INLINE_LIST' => '0', 'SIZE' => '9')));
            $radios->attach(form_input_radio_entry('periodic_when', 'monthly', $frequency == 'monthly', $monthly_desc, null, ''));
            $fields->attach(form_input_radio(do_lang('PERIODIC_WHEN_CHOICE'), '', 'periodic_when', $radios, true));

            $radios = new Tempcode();
            $radios->attach(form_input_radio_entry('periodic_for', 'all', false, do_lang_tempcode('CREATE_PERIODIC_FOR_ALL'), null, ''));
            $radios->attach(form_input_radio_entry('periodic_for', 'future', true, do_lang_tempcode('CREATE_PERIODIC_FOR_FUTURE'), null, ''));
            $fields->attach(form_input_radio(do_lang('CREATE_PERIODIC_FOR'), '', 'periodic_for', $radios, true));
        }

        return do_template('FORM_SCREEN', array(
            '_GUID' => '0b2a4825ec586d9ff557026d9a1e0cca',
            'TITLE' => $this->title,
            'TEXT' => (($periodic_action == 'make' || $periodic_action == 'replace') ? do_lang_tempcode('PERIODIC_NO_EDIT') : do_lang_tempcode('NEWSLETTER_SEND_TEXT')),
            'HIDDEN' => $hidden,
            'FIELDS' => $fields->evaluate()/*FUDGE*/,
            'SUBMIT_ICON' => 'tabs__preview',
            'SUBMIT_NAME' => $submit_name,
            'URL' => $post_url,
            'SUPPORT_AUTOSAVE' => true,
        ));
    }

    /**
     * The UI to confirm sending of our newsletter.
     *
     * @return Tempcode The UI
     */
    public function confirm_send()
    {
        $message = post_param_string('message');
        $subject = post_param_string('subject');
        $lang = choose_language($this->title);

        $template = post_param_string('template', 'MAIL');
        $in_full = post_param_integer('in_full', 0);

        $html_only = post_param_integer('html_only', 0);
        $from_email = post_param_string('from_email', '');
        $from_name = post_param_string('from_name', '');

        $extra_post_data = array();
        require_code('uploads');
        $_csv_data = post_param_string('csv_data', null);
        if (!is_null($_csv_data)) {
            $extra_post_data['csv_data'] = $_csv_data;
        } else {
            if (((is_plupload(true)) && (array_key_exists('file', $_FILES))) || ((array_key_exists('file', $_FILES)) && (is_uploaded_file($_FILES['file']['tmp_name'])))) {
                $__csv_data = array();
                safe_ini_set('auto_detect_line_endings', '1');
                $myfile = fopen($_FILES['file']['tmp_name'], GOOGLE_APPENGINE ? 'rb' : 'rt');
                $del = ',';
                $csv_test_line = fgetcsv($myfile, 4096, $del);
                if ((count($csv_test_line) == 1) && (strpos($csv_test_line[0], ';') !== false)) {
                    $del = ';';
                }
                rewind($myfile);
                while (($csv_line = fgetcsv($myfile, 4096, $del)) !== false) {
                    $__csv_data[] = $csv_line;
                }
                fclose($myfile);

                $extra_post_data['csv_data'] = serialize($__csv_data);
            }
        }

        if (post_param_integer('make_periodic', 0) == 1) {
            // We're making a periodic newsletter. Thus we need to pass this info
            // through to the next step
            $extra_post_data['make_periodic'] = '1';

            // Re-generate preview from latest chosen_categories
            $message = $this->_generate_whatsnew_comcode(post_param_string('chosen_categories', ''), $in_full, $lang, post_param_date('cutoff'));
        }

        $address = $GLOBALS['FORUM_DRIVER']->get_member_email_address(get_member());
        if ($address == '') {
            $address = get_option('staff_address');
        }
        $username = $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true);

        $message = newsletter_variable_substitution($message, $subject, '', '', do_lang('EXAMPLE'), $address, 'test', '');

        require_code('mail');

        require_code('media_renderer');
        push_media_mode(peek_media_mode() | MEDIA_LOWFI);
        require_code('tempcode_compiler');
        $in_html = false;
        if (strpos($message, '<html') !== false) {
            $_preview = template_to_tempcode($message);
            $in_html = true;
        } else {
            $comcode_version = comcode_to_tempcode($message, get_member(), true);
            $_preview = do_template(
                $template,
                array(
                    '_GUID' => 'b081cf9104748b090f63b6898027985e',
                    'TITLE' => $subject,
                    'CSS' => css_tempcode(true, true, $comcode_version->evaluate()),
                    'LANG' => get_site_default_lang(),
                    'LOGOURL' => get_logo_url(''),
                    'CONTENT' => $comcode_version
                ),
                null,
                false,
                null,
                '.tpl',
                'templates',
                $GLOBALS['FORUM_DRIVER']->get_theme('')
            );
            $in_html = ($html_only == 1);
        }
        $text_preview = ($html_only == 1) ? '' : comcode_to_clean_text(static_evaluate_tempcode(template_to_tempcode($message)));
        require_code('mail');
        $preview_subject = $subject;
        if (post_param_integer('make_periodic', 0) == 1) {
            $preview_subject .= ' - ' . get_timezoned_date(time(), false, false, false, true);
        }
        $preview_subject = do_lang('NEWSLETTER_PREVIEW_SUBJECT', $preview_subject);
        require_code('comcode_compiler');
        $preview = do_template('NEWSLETTER_CONFIRM_WRAP', array('_GUID' => '02bd5a782620141f8589e647e2c6d90b', 'TEXT_PREVIEW' => $text_preview, 'PREVIEW' => $_preview, 'SUBJECT' => $subject));
        pop_media_mode();

        mail_wrap($preview_subject, ($html_only == 1) ? $_preview->evaluate() : $message, array($address), $username/*do_lang('NEWSLETTER_SUBSCRIBER_DEFAULT_NAME',get_site_name())*/, $from_email, $from_name, 3, null, true, null, true, $in_html, true, $template);

        require_code('templates_confirm_screen');
        return confirm_screen($this->title, $preview, 'send', get_param_string('old_type', 'new'), $extra_post_data);
    }

    /**
     * The actualiser to send a newsletter.
     *
     * @return Tempcode The UI
     */
    public function send_message()
    {
        $lang = choose_language($this->title);
        if (is_object($lang)) {
            return $lang;
        }

        if (get_param_string('old_type', '') == 'whatsnew') {
            set_value('newsletter_whatsnew', strval(time()));
        }

        $message = post_param_string('message');
        $subject = post_param_string('subject');
        $csv_data = post_param_string('csv_data', ''); // serialized PHP array

        $template = post_param_string('template', 'MAIL');
        $in_full = post_param_integer('in_full', 0);

        $html_only = post_param_integer('html_only', 0);
        $from_email = post_param_string('from_email', '');
        $from_name = post_param_string('from_name', '');
        $priority = post_param_integer('priority', 3);

        $newsletters = $GLOBALS['SITE_DB']->query_select('newsletters', array('id'));
        $send_details = array();
        foreach ($newsletters as $newsletter) {
            $send_details[strval($newsletter['id'])] = post_param_integer(strval($newsletter['id']), 0);
        }
        if (get_forum_type() == 'cns') {
            $groups = $GLOBALS['FORUM_DRIVER']->get_usergroup_list();
            foreach (array_keys($groups) as $id) {
                $send_details['g' . strval($id)] = post_param_integer('g' . strval($id), 0);
            }
            $send_details['-1'] = post_param_integer('-1', 0);
        }

        if (post_param_integer('make_periodic', 0) == 1) {
            // We're a periodic newsletter, so we don't actually want to be sent
            // out now. Rather, we store the newsletter settings so that it can be
            // regenerated as needed.

            // Next we store all of our settings in the newsletter_periodic table
            $when = post_param_string('periodic_when');
            $day = 1;
            if ($when == 'monthly') {
                $day = post_param_integer('periodic_monthly') % 29;
            } elseif ($when == 'biweekly') {
                $day = post_param_integer('periodic_weekday_biweekly', 5);
            } elseif ($when == 'weekly') {
                $day = post_param_integer('periodic_weekday_weekly', 5);
            }
            require_lang('dates');
            $week_days = array(1 => do_lang('MONDAY'), 2 => do_lang('TUESDAY'), 3 => do_lang('WEDNESDAY'), 4 => do_lang('THURSDAY'), 5 => do_lang('FRIDAY'), 6 => do_lang('SATURDAY'), 7 => do_lang('SUNDAY'));
            if ($when == 'weekly') {
                $each = $week_days[$day];
            } elseif ($when == 'biweekly') {
                $each = $week_days[$day];
            } else {
                $suffix = gmdate('S', gmmktime(0, 0, 0, 1, $day, 1990));
                $each = strval($day) . $suffix;
            }

            $matches = array();
            if (preg_match('#^replace_existing\_(\d+)$#', post_param_string('periodic_choice', ''), $matches) != 0) {
                $last_sent = null;
                if (post_param_string('periodic_for') != 'future') {
                    $last_sent = 0;
                }
                edit_periodic_newsletter(intval($matches[1]), $subject, post_param_string('chosen_categories', ''), $lang, serialize($send_details), $html_only, $from_email, $from_name, $priority, $csv_data, $when, $day, $in_full, $template, $last_sent);
                $message = do_lang('PERIODIC_SUCCESS_MESSAGE_EDIT', $when, $each);
            } else {
                $last_sent = (post_param_string('periodic_for') == 'future') ? time() : 0;

                add_periodic_newsletter($subject, post_param_string('chosen_categories', ''), $lang, serialize($send_details), $html_only, $from_email, $from_name, $priority, $csv_data, $when, $day, $in_full, $template, $last_sent);
                $message = do_lang('PERIODIC_SUCCESS_MESSAGE_ADD', $when, $each);
            }

            $url = build_url(array('page' => 'admin_newsletter', 'type' => 'browse', 'redirected' => '1'), get_module_zone('admin_newsletter'));
            return redirect_screen(do_lang('SUCCESS'), $url, $message, false, 'inform');
        }

        if (addon_installed('calendar')) {
            $schedule = post_param_date('schedule');
            if (!is_null($schedule)) {
                require_code('calendar');
                require_code('calendar2');
                $send_details_string_exp = '';
                foreach ($send_details as $key => $val) {
                    $send_details_string_exp .= '"' . str_replace("\n", '\n', addslashes($key)) . '"=>"' . str_replace("\n", '\n', addslashes($val)) . '",';
                }
                $schedule_code = ':require_code(\'newsletter\'); actual_send_newsletter("' . php_addslashes($message) . '","' . php_addslashes($subject) . '","' . php_addslashes($lang) . '",array(' . $send_details_string_exp . '),' . strval($html_only) . ',"' . php_addslashes($from_email) . '","' . php_addslashes($from_name) . '",' . strval($priority) . ',"' . php_addslashes($csv_data) . '","' . php_addslashes($template) . '");';
                $start_year = intval(date('Y', $schedule));
                $start_month = intval(date('m', $schedule));
                $start_day = intval(date('d', $schedule));
                $start_hour = intval(date('H', $schedule));
                $start_minute = intval(date('i', $schedule));
                $event_id = add_calendar_event(db_get_first_id(), '', null, 0, do_lang('NEWSLETTER_SEND', $subject), $schedule_code, 3, $start_year, $start_month, $start_day, 'day_of_month', $start_hour, $start_minute);
                regenerate_event_reminder_jobs($event_id);

                return inform_screen($this->title, do_lang_tempcode('NEWSLETTER_DEFERRED', get_timezoned_date($schedule)));
            }
        }

        log_it('NEWSLETTER_SEND');

        return actual_send_newsletter($message, $subject, $lang, $send_details, $html_only, $from_email, $from_name, $priority, $csv_data, $template);
    }

    /**
     * The UI to select to view a past newsletter.
     *
     * @return Tempcode The UI
     */
    public function archive()
    {
        $lang = choose_language($this->title);
        if (is_object($lang)) {
            return $lang;
        }

        $newsletters = new Tempcode();
        $where = multi_lang() ? array('language' => $lang) : null;
        $rows = $GLOBALS['SITE_DB']->query_select('newsletter_archive', array('id', 'subject', 'date_and_time'), $where, 'ORDER BY date_and_time DESC');
        foreach ($rows as $newsletter) {
            $newsletters->attach(form_input_list_entry(strval($newsletter['id']), false, $newsletter['subject']));
        }
        if ($newsletters->is_empty()) {
            inform_exit(do_lang_tempcode('NO_ENTRIES'));
        }
        require_code('form_templates');
        $fields = form_input_list(do_lang_tempcode('NEWSLETTER'), '', 'id', $newsletters, null, true);
        $hidden = form_input_hidden('lang', $lang);

        $submit_name = do_lang_tempcode('VIEW');
        $post_url = build_url(array('page' => '_SELF', 'type' => 'view'), '_SELF', null, false, true);

        return do_template('FORM_SCREEN', array('_GUID' => 'ee295e41dc86c4583c123e6e0e445380', 'GET' => true, 'SKIP_WEBSTANDARDS' => true, 'HIDDEN' => $hidden, 'TITLE' => $this->title, 'TEXT' => '', 'FIELDS' => $fields, 'SUBMIT_ICON' => 'menu___generic_admin__view_archive', 'SUBMIT_NAME' => $submit_name, 'URL' => $post_url));
    }

    /**
     * The UI to view a past newsletter.
     *
     * @return Tempcode The UI
     */
    public function view()
    {
        $id = get_param_integer('id');

        $rows = $GLOBALS['SITE_DB']->query_select('newsletter_archive', array('*'), array('id' => $id), '', 1);

        $time = get_timezoned_date($rows[0]['date_and_time']);
        $subject = $rows[0]['subject'];
        $message = $rows[0]['newsletter'];
        $language = $rows[0]['language'];
        $level = $rows[0]['importance_level'];
        require_code('lang2');
        $language = lookup_language_full_name($rows[0]['language']);

        $message = newsletter_variable_substitution($message, $subject, '', '', do_lang('EXAMPLE'), 'test@example.com', 'test', '');

        require_code('mail');

        // TODO: Change to use new function in v11
        require_code('media_renderer');
        push_media_mode(peek_media_mode() | MEDIA_LOWFI);
        require_code('tempcode_compiler');
        $in_html = false;
        if (strpos($message, '<html') !== false) {
            $_preview = template_to_tempcode($message);
        } else {
            $_preview = comcode_to_tempcode($message, get_member(), true);
        }
        pop_media_mode();

        require_code('templates_map_table');
        return map_table_screen(get_screen_title('NEWSLETTER'), array(
            'DATE_TIME' => $time,
            'LANGUAGE' => $language,
            'SUBSCRIPTION_LEVEL' => integer_format($level),
            'SUBJECT' => $subject,
            'MESSAGE' => $_preview,
        ));
    }

    /**
     * Get Tempcode for adding/editing form.
     *
     * @param  SHORT_TEXT $title The title
     * @param  LONG_TEXT $description The description
     * @return array A pair: The input fields, Hidden fields
     */
    public function get_form_fields($title = '', $description = '')
    {
        $fields = new Tempcode();
        $fields->attach(form_input_line(do_lang_tempcode('TITLE'), do_lang_tempcode('DESCRIPTION_TITLE'), 'title', $title, true));
        $fields->attach(form_input_text(do_lang_tempcode('DESCRIPTION'), do_lang_tempcode('DESCRIPTION_DESCRIPTION'), 'description', $description, true));

        return array($fields, new Tempcode());
    }

    /**
     * Standard crud_module table function.
     *
     * @param  array $url_map Details to go to build_url for link to the next screen.
     * @return array A pair: The choose table, Whether reordering is supported from this screen.
     */
    public function create_selection_list_choose_table($url_map)
    {
        require_code('templates_results_table');

        $current_ordering = get_param_string('sort', 'title ASC', true);
        list($sortable, $sort_order) = array(substr($current_ordering, 0, strrpos($current_ordering, ' ')), substr($current_ordering, strrpos($current_ordering, ' ') + 1));
        $sortables = array(
            'title' => do_lang_tempcode('TITLE'),
        );
        if (db_has_subqueries($GLOBALS['SITE_DB']->connection_read)) {
            $sortables['(SELECT COUNT(*) FROM ' . get_table_prefix() . 'newsletter_subscribers n JOIN ' . get_table_prefix() . 'newsletter_subscribe s ON n.id=s.newsletter_id WHERE code_confirm=0)'] = do_lang_tempcode('COUNT_MEMBERS');
        }
        if (((strtoupper($sort_order) != 'ASC') && (strtoupper($sort_order) != 'DESC')) || (!array_key_exists($sortable, $sortables))) {
            log_hack_attack_and_exit('ORDERBY_HACK');
        }

        $header_row = results_field_title(array(
            do_lang_tempcode('TITLE'),
            do_lang_tempcode('COUNT_MEMBERS'),
            do_lang_tempcode('ACTIONS'),
        ), $sortables, 'sort', $sortable . ' ' . $sort_order);

        $fields = new Tempcode();

        require_code('form_templates');
        list($rows, $max_rows) = $this->get_entry_rows(false, $current_ordering);
        foreach ($rows as $row) {
            $edit_link = build_url($url_map + array('id' => $row['id']), '_SELF');

            $num_readers = $GLOBALS['SITE_DB']->query_select_value('newsletter_subscribers n JOIN ' . get_table_prefix() . 'newsletter_subscribe s ON n.id=s.newsletter_id', 'COUNT(*)', array('code_confirm' => 0));

            $fields->attach(results_entry(array(get_translated_text($row['title']), integer_format($num_readers), protect_from_escaping(hyperlink($edit_link, do_lang_tempcode('EDIT'), false, false, do_lang('EDIT') . ' #' . strval($row['id'])))), true));
        }

        return array(results_table(do_lang($this->menu_label), get_param_integer('start', 0), 'start', either_param_integer('max', 20), 'max', $max_rows, $header_row, $fields, $sortables, $sortable, $sort_order), false);
    }

    /**
     * Standard crud_module list function.
     *
     * @return Tempcode The selection list
     */
    public function create_selection_list_entries()
    {
        $_m = $GLOBALS['SITE_DB']->query_select('newsletters', array('id', 'title'));
        $entries = new Tempcode();
        foreach ($_m as $m) {
            $entries->attach(form_input_list_entry(strval($m['id']), false, get_translated_text($m['title'], $GLOBALS['SITE_DB'])));
        }

        return $entries;
    }

    /**
     * Standard crud_module edit form filler.
     *
     * @param  ID_TEXT $id The entry being edited
     * @return array A pair: The input fields, Hidden fields
     */
    public function fill_in_edit_form($id)
    {
        $m = $GLOBALS['SITE_DB']->query_select('newsletters', array('*'), array('id' => intval($id)), '', 1);
        if (!array_key_exists(0, $m)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
        }
        $r = $m[0];

        return $this->get_form_fields(get_translated_text($r['title']), get_translated_text($r['description']));
    }

    /**
     * Standard crud_module add actualiser.
     *
     * @return ID_TEXT The entry added
     */
    public function add_actualisation()
    {
        $title = post_param_string('title');
        $description = post_param_string('description');

        $id = add_newsletter($title, $description);

        return strval($id);
    }

    /**
     * Standard crud_module edit actualiser.
     *
     * @param  ID_TEXT $id The entry being edited
     */
    public function edit_actualisation($id)
    {
        $title = post_param_string('title');
        $description = post_param_string('description');

        edit_newsletter(intval($id), $title, $description);
    }

    /**
     * Standard crud_module delete actualiser.
     *
     * @param  ID_TEXT $id The entry being deleted
     */
    public function delete_actualisation($id)
    {
        delete_newsletter(intval($id));
    }
}
