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

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

    /**
     * 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('COMCODE_PAGE_MANAGEMENT', 'menu/cms/comcode_page_edit'),
            'generate_page_sitemap' => array('GENERATE_PAGE_SITEMAP', 'tool_buttons/sitemap'),
        );

        if ($support_crosslinks) {
            require_code('fields');
            $ret += manage_custom_fields_entry_points('comcode_page');
        }

        return $ret;
    }

    /**
     * Find privileges defined as overridable by this module.
     *
     * @return array A map of privileges that are overridable; privilege to 0 or 1. 0 means "not category overridable". 1 means "category overridable".
     */
    public function get_privilege_overrides()
    {
        return array('submit_highrange_content' => array(1, 'COMCODE_PAGE_ADD'), 'edit_highrange_content' => array(1, 'COMCODE_PAGE_EDIT'), 'edit_own_highrange_content' => array(1, 'COMCODE_PAGE_OWN_EDIT'), 'bypass_validation_highrange_content' => array(1, 'BYPASS_COMCODE_PAGE_VALIDATION'));
    }

    public $title;
    public $page_link;
    public $zone;
    public $file;

    /**
     * Module pre-run function. Allows us to know metadata for <head> before we start streaming output.
     *
     * @return ?Tempcode Tempcode indicating some kind of exceptional output (null: none).
     */
    public function pre_run()
    {
        $type = get_param_string('type', 'browse');

        require_lang('zones');

        require_code('content2');

        inform_non_canonical_parameter('parent_page');

        set_helper_panel_tutorial('tut_comcode_pages');

        if ($type == 'browse') {
            breadcrumb_set_self(do_lang_tempcode('COMCODE_PAGES'));

            set_helper_panel_text(comcode_lang_string('DOC_COMCODE_PAGE_EDIT'));

            $this->title = get_screen_title('COMCODE_PAGE_EDIT');
        }

        if ($type == '_edit') {
            require_lang('menus');
            set_helper_panel_text(comcode_lang_string('DOC_WRITING'));

            // Work out what we're editing, and where it's coming from (support for two page_link specifying parameters for destination)
            $page_link = filter_naughty(get_param_string('page_link', ''));
            if ($page_link == '') {
                $page_link = get_param_string('page_link_2');
            }
            if (strpos($page_link, ':') === false) {
                $page_link = ':' . $page_link;
            }
            $page_link_parts = explode(':', $page_link);
            if (count($page_link_parts) != 2) {
                warn_exit(do_lang_tempcode('ZONE_COLON_PAGE'));
            }
            $zone = $page_link_parts[0];
            $file = $page_link_parts[1];

            if ($page_link != '') {
                breadcrumb_set_self(do_lang_tempcode('EDIT'));
            }
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse:lang=' . get_param_string('lang', ''), do_lang_tempcode('menus:_COMCODE_PAGES'))));

            $this->title = get_screen_title(($file == '') ? 'COMCODE_PAGE_ADD' : '_COMCODE_PAGE_EDIT', true, array(escape_html($zone), escape_html($file)));

            $this->page_link = $page_link;
            $this->zone = $zone;
            $this->file = $file;
        }

        if ($type == '__edit') {
            breadcrumb_set_self(do_lang_tempcode('DONE'));

            $this->title = get_screen_title('COMCODE_PAGE_EDIT');
        }

        if ($type == 'generate_page_sitemap') {
            $this->title = get_screen_title('GENERATE_PAGE_SITEMAP');

            breadcrumb_set_parents(array(array('_SELF:_SELF:browse:lang=' . get_param_string('lang', ''), do_lang_tempcode('menus:_COMCODE_PAGES'))));
        }

        return null;
    }

    /**
     * Execute the module.
     *
     * @return Tempcode The result of execution.
     */
    public function run()
    {
        require_code('zones2');
        require_code('zones3');
        require_code('files');

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

        if ($type == 'browse') {
            return $this->edit();
        }
        if ($type == '_edit') {
            return $this->_edit();
        }
        if ($type == '__edit') {
            return $this->__edit();
        }
        if ($type == 'generate_page_sitemap') {
            return $this->generate_page_sitemap();
        }

        return new Tempcode();
    }

    /**
     * The do-next manager for after content management.
     *
     * @param  Tempcode $title The title (output of get_screen_title)
     * @param  ?ID_TEXT $page The name of the page just handled (null: none)
     * @param  ID_TEXT $zone The name of the zone just handled (blank: none/welcome-zone)
     * @param  Tempcode $completion_text The text to show (blank: default)
     * @return Tempcode The UI
     */
    public function do_next_manager($title, $page, $zone, $completion_text)
    {
        if (!addon_installed('page_management')) {
            return redirect_screen($title, build_url(array('page' => '_SELF', 'type' => 'browse'), '_SELF'), $completion_text);
        }

        return sitemap_do_next_manager($title, $page, $zone, $completion_text);
    }

    /**
     * Find all pages using a disk search.
     *
     * @param  LANGUAGE_NAME $lang The language we are searching for pages of
     * @param  ?array $zone_filter List of zones to limit to (null: none)
     * @param  boolean $check_permissions Whether to check edit permissions
     * @return array The map (page name => map [path & row])
     */
    public function get_comcode_files_list_disk_search($lang, $zone_filter, $check_permissions = true)
    {
        $zones = find_all_zones();
        $out = array();
        foreach ($zones as $zone) {
            if ($zone_filter !== null && !in_array($zone, $zone_filter)) {
                continue;
            }

            $_out = array();
            foreach (array_unique(array(fallback_lang(), get_site_default_lang(), $lang)) as $_lang) {
                $_out += find_all_pages($zone, 'comcode_custom/' . $_lang, 'txt', false, null, FIND_ALL_PAGES__NEWEST);
                $_out += find_all_pages($zone, 'comcode/' . $_lang, 'txt', false, null, FIND_ALL_PAGES__NEWEST);
            }

            foreach ($_out as $page => $subdir) {
                if (is_integer($page)) {
                    $page = strval($page);
                }

                if ($check_permissions) {
                    $resource_owner = $GLOBALS['SITE_DB']->query_select_value_if_there('comcode_pages', 'p_submitter', array('the_zone' => $zone, 'the_page' => $page));
                    if (!has_edit_permission('high', get_member(), $resource_owner, 'cms_comcode_pages')) {
                        continue;
                    }
                }

                $located = _request_page($page, $zone, null, $lang);
                if (($located !== false) && (strpos($located[0], 'COMCODE') !== false)) {
                    $_full_path = $located[count($located) - 1];
                    $full_path = get_custom_file_base() . '/' . $_full_path;
                    if (!is_file($full_path)) {
                        $full_path = get_file_base() . '/' . $_full_path;
                    }

                    $out[$zone . ':' . $page] = array(
                        $full_path, // page path
                        null, // row
                    );
                }
            }
        }

        return $out;
    }

    /**
     * The UI to choose a page to edit.
     *
     * @return Tempcode The UI
     */
    public function edit()
    {
        require_code('form_templates');
        require_code('templates_results_table');

        $number_pages_parsed_for_titles = 0;

        $GLOBALS['NO_QUERY_LIMIT'] = true;

        $start = get_param_integer('start', 0);
        $max = get_param_integer('max', 25);

        $_zone_filter = either_param_string('zone_filter', null);
        if ($_zone_filter !== null) {
            $zone_filter = explode(',', $_zone_filter);
        } else {
            $zone_filter = null;
        }

        $filter = either_param_string('filter', null);
        if (empty($filter)) {
            $filter = null;
        }

        // Choose language
        $lang = choose_language($this->title, true);
        if (is_object($lang)) {
            return $lang;
        }

        // Form for adding new
        $fields = new Tempcode();
        $add_new_permission = has_add_comcode_page_permission();
        if ($add_new_permission) {
            if (get_option('collapse_user_zones') == '1') {
                $sample_page_name = ':example_new_page';
            } else {
                $sample_page_name = 'site:example_new_page';
            }
            $fields->attach(form_input_line(do_lang_tempcode('PAGE'), do_lang_tempcode('DESCRIPTION_NEW_COMCODE_PAGE'), 'page_link_2', '', true, null, 80, 'text', $sample_page_name, '([' . URL_CONTENT_REGEXP_JS . ']*:)?[' . URL_CONTENT_REGEXP_JS . ']+'));

            $template_list = new Tempcode();
            $template_list->attach(form_input_list_entry('', true, do_lang('NA')));
            $templates = get_templates_list();
            foreach ($templates as $template => $template_title) {
                $template_list->attach(form_input_list_entry($template, false, $template_title));
            }
            if (!$template_list->is_empty()) {
                $fields->attach(form_input_list(do_lang_tempcode('PAGE_TEMPLATE'), do_lang_tempcode('PAGE_TEMPLATE_DESCRIPTION'), 'page_template', $template_list, null, false, false));
            }

            $submit_name = do_lang_tempcode('ADD');
        } else {
            $submit_name = null;
        }

        // URLs etc...

        $search_url = build_url(array('page' => 'search', 'id' => 'comcode_pages'), get_module_zone('search'));
        $sitemap_zone = get_page_zone('sitemap', false);
        if ($sitemap_zone !== null) {
            $archive_url = build_url(array('page' => 'sitemap'), $sitemap_zone);
        } else {
            $archive_url = build_url(array('page' => ''), '');
        }
        $sitemap_url = build_url(array('page' => '_SELF', 'type' => 'generate_page_sitemap'), '_SELF');
        $text = paragraph(do_lang_tempcode('CHOOSE_EDIT_TABLE_EXTRA_COMCODE_PAGES', escape_html($search_url->evaluate()), escape_html($archive_url->evaluate()), escape_html($sitemap_url->evaluate())));

        // Sorting
        $current_ordering = get_param_string('sort', 'page ASC');
        if (strpos($current_ordering, ' ') === false) {
            warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
        }
        list($sortable, $sort_order) = explode(' ', $current_ordering, 2);
        $sortables = array(
            'page_title' => do_lang_tempcode('TITLE'),
            'page' => do_lang_tempcode('PAGE'),
            'zone' => do_lang_tempcode('ZONE'),
            'page_link' => do_lang_tempcode('PAGE_LINK'),
            'order' => do_lang_tempcode('ORDER'),
        );
        if (((strtoupper($sort_order) != 'ASC') && (strtoupper($sort_order) != 'DESC')) || (!array_key_exists($sortable, $sortables))) {
            log_hack_attack_and_exit('ORDERBY_HACK');
        }

        // Get file details
        $found_via_query = false;
        $total_known_pages = $GLOBALS['SITE_DB']->query_select_value('comcode_pages', 'COUNT(*)');
        $find_via_query = true;
        if (get_param_integer('force_manual_scan', null) === 0) {
            $find_via_query = true;
        } elseif ($total_known_pages < $max) {
            $find_via_query = false;
        } elseif (get_param_integer('force_manual_scan', null) === 1) {
            $find_via_query = false;
        } elseif (
            ($total_known_pages < 300) &&
            ($GLOBALS['SITE_DB']->query_select_value_if_there('comcode_pages c LEFT JOIN ' . get_table_prefix() . 'cached_comcode_pages a ON c.the_page=a.the_page AND c.the_zone=a.the_zone', 'c.the_page', array('a.the_page' => null)) !== null)
        ) {
            $find_via_query = false;
        }
        if ($find_via_query) { // Lots of pages so do it via querying
            $found_via_query = true;

            // Sorting
            $orderer = 'p_add_date ASC';
            switch ($sortable) {
                case 'page_title':
                    $orderer = $GLOBALS['SITE_DB']->translate_field_ref('cc_page_title') . ' ' . $sort_order;
                    break;
                case 'page':
                    $orderer = 'c.the_page ' . $sort_order;
                    break;
                case 'zone':
                    $orderer = 'c.the_zone ' . $sort_order;
                    break;
                case 'page_link':
                    $orderer = 'c.the_zone ' . $sort_order . ',c.the_page ' . $sort_order;
                    break;
            }

            // De-duplication
            $group_by = '';
            if (can_arbitrary_groupby()) {
                $group_by = ' GROUP BY c.the_zone,c.the_page';
            }

            // Where map
            $where_map = '1=1';
            if (!has_some_edit_comcode_page_permission(COMCODE_EDIT_ANY)) {
                if (!has_some_edit_comcode_page_permission(COMCODE_EDIT_OWN)) { // Nothing global, so go per-zone
                    $zone_editability = get_comcode_page_editability_per_zone();
                    if (count($zone_editability) != 0) {
                        $where_map .= ' AND (';
                        foreach ($zone_editability as $zi => $zone_editability_pair) {
                            if ($zi != 0) {
                                $where_map .= ' OR ';
                            }
                            if (($zone_editability_pair[1] & COMCODE_EDIT_ANY) == 0) {
                                $where_map .= '(' . db_string_equal_to('c.the_zone', $zone_editability_pair[0]) . ' AND p_submitter=' . strval(get_member()) . ')';
                            } else {
                                $where_map .= db_string_equal_to('c.the_zone', $zone_editability_pair[0]);
                            }
                        }
                        $where_map .= ')';
                    } else {
                        access_denied('ADD_OR_EDIT_COMCODE_PAGES'); // Nothing at all
                    }
                } else {
                    $where_map .= ' AND p_submitter=' . strval(get_member());
                }
            } else {
                // No additional filter; NB: this does assume no negative overrides are in place; if they are, an error will be shown when clicking through
            }
            if ($filter !== null) {
                $where_map .= ' AND (';
                $search_fields = array(
                    'c.the_zone',
                    'c.the_page',
                    'p_parent_page',
                    $GLOBALS['SITE_DB']->translate_field_ref('cc_page_title'),
                    $GLOBALS['SITE_DB']->translate_field_ref('string_index'),
                );
                foreach ($search_fields as $i => $field) {
                    if ($i != 0) {
                        $where_map .= ' OR ';
                    }
                    $where_map .= $field . ' LIKE \'' . db_encode_like('%' . $filter . '%') . '\'';
                }
                $filter_member_id = $GLOBALS['FORUM_DRIVER']->get_member_from_username($filter);
                if ($filter_member_id !== null) {
                    $where_map .= ' OR p_submitter=' . strval($filter_member_id);
                }
                $where_map .= ')';
            }
            if ($zone_filter !== null) {
                $where_map .= ' AND c.the_zone IN (';
                foreach ($zone_filter as $i => $zone) {
                    if ($i != 0) {
                        $where_map .= ',';
                    }
                    $where_map .= '\'' . db_escape_string($zone) . '\'';
                }
                $where_map .= ')';
            }

            // Do queries
            $ttable = get_table_prefix() . 'comcode_pages c LEFT JOIN ' . get_table_prefix() . 'cached_comcode_pages a ON c.the_page=a.the_page AND c.the_zone=a.the_zone';
            $sql = 'SELECT c.*,cc_page_title FROM ' . $ttable . ' WHERE ' . $where_map . $group_by . ' ORDER BY ' . $orderer;
            $lang_fields = array('cc_page_title' => '?SHORT_TRANS', 'string_index' => '?LONG_TRANS');
            $page_rows = $GLOBALS['SITE_DB']->query($sql, $max, $start, false, false, $lang_fields);
            $max_rows = $GLOBALS['SITE_DB']->query_value_if_there('SELECT COUNT(*) FROM (SELECT DISTINCT c.the_zone,c.the_page FROM ' . $ttable . ' WHERE ' . $where_map . ') x', false, false, $lang_fields);

            // Put together meta-data
            $files_list = array();
            foreach ($page_rows as $row) {
                $located = _request_page($row['the_page'], $row['the_zone'], null, $lang);
                if ($located !== false && $located[0] != 'REDIRECT') {
                    $_zone = $located[count($located) - 1];
                    $page_path = get_custom_file_base() . (($_zone == '') ? '' : '/') . $_zone;
                    if (!is_file($page_path)) {
                        $page_path = get_file_base() . (($_zone == '') ? '' : '/') . $_zone;
                    }

                    $files_list[$row['the_zone'] . ':' . $row['the_page']] = array(
                        $page_path, // page_path
                        $row, // row
                    );
                }
            }
        }
        if (!$found_via_query) {
            $files_list = $this->get_comcode_files_list_disk_search($lang, $zone_filter);
        }

        // Gather meta-data, enough for sorting
        $rows = array();
        foreach ($files_list as $page_link => $path_bits) {
            list($zone, $page) = explode(':', $page_link, 2);
            if (!is_string($page)) {
                $page = strval($page);
            }

            // Check permissions
            if (!has_actual_page_access(get_member(), $page, $zone)) {
                continue;
            }

            // We need to separately read from DB to work out metadata?
            $db_row = mixed();
            if ($path_bits[1] === null) {
                $db_rows = $GLOBALS['SITE_DB']->query_select('comcode_pages c LEFT JOIN ' . get_table_prefix() . 'cached_comcode_pages a ON c.the_page=a.the_page AND c.the_zone=a.the_zone', array('c.*', 'cc_page_title'), array('c.the_zone' => $zone, 'c.the_page' => $page), '', 1);
                if ((!isset($db_rows[0])) && ($number_pages_parsed_for_titles < 3/*Too intensive to do much at all*/) && (has_caching_for('comcode_page'))) {
                    $result = request_page($page, false, $zone, 'comcode_custom', true);
                    $db_rows = $GLOBALS['SITE_DB']->query_select('comcode_pages c LEFT JOIN ' . get_table_prefix() . 'cached_comcode_pages a ON c.the_page=a.the_page AND c.the_zone=a.the_zone', array('c.*', 'cc_page_title'), array('c.the_zone' => $zone, 'c.the_page' => $page), '', 1);
                    $number_pages_parsed_for_titles++;
                }
                $db_row = isset($db_rows[0]) ? $db_rows[0] : null;
            } else { // Ah, no we got everything from DB in one go
                $db_row = $path_bits[1];
            }

            // Work out metadata
            if (is_null($db_row)) {
                $page_title = get_comcode_page_title_from_disk($path_bits[0]);
                $order = null;
                $parent_page = '';
                $submitter = null;
                $add_date = null;
                $validated = null;
            } else {
                if ($db_row['cc_page_title'] === null) {
                    require_code('zones2');
                    $page_title = get_comcode_page_title_from_disk($path_bits[0]);
                } else {
                    $page_title = get_translated_text($db_row['cc_page_title'], null, null, true);
                }
                $order = $db_row['p_order'];
                $parent_page = $db_row['p_parent_page'];
                $submitter = $db_row['p_submitter'];
                $add_date = $db_row['p_add_date'];
                $validated = $db_row['p_validated'];
            }
            $page_path = $path_bits[0];

            // Put into array
            $rows[] = array(
                'page_title' => $page_title,
                'page' => $page,
                'zone' => $zone,
                'page_link' => $page_link,
                'order' => $order,
                'parent_page' => $parent_page,
                'submitter' => $submitter,
                'add_date' => $add_date,
                'validated' => $validated,
                'page_path' => $page_path,
            );
        }

        // Search filtering
        if ($filter !== null) {
            $search_fields = array(
                'zone',
                'page',
                'parent_page',
                'page_title',
            );
            foreach ($rows as $i => $row) {
                foreach ($search_fields as $field) {
                    if (stripos($row[$field], $filter) !== false) {
                        continue 2;
                    }
                }

                if (($row['page_path'] !== null) && (stripos(file_get_contents($row['page_path']), $filter) !== false)) {
                    continue;
                }

                // Does not match
                unset($rows[$i]);
            }
        }

        // Manual sorting
        if (!$found_via_query) {
            $max_rows = count($rows);

            sort_maps_by($rows, $sortable);
            if ($sort_order == 'DESC') {
                $rows = array_reverse($rows);
            }
        }

        // Results table
        $show_helper_panel = (!isset($_COOKIE['hide_helper_panel'])) || ($_COOKIE['hide_helper_panel'] != '1');
        $columns = array();
        $columns[] = do_lang_tempcode('TITLE');
        $columns[] = do_lang_tempcode('ZONE');
        $columns[] = do_lang_tempcode('PAGE');
        if (!$show_helper_panel) {
            $columns[] = do_lang_tempcode('PARENT_PAGE');
            $columns[] = do_lang_tempcode('metadata:OWNER');
            $columns[] = do_lang_tempcode('ADDED');
            $columns[] = protect_from_escaping(do_template('COMCODE_ABBR', array('_GUID' => 'bd3e38aa0885f27174b4ccb4515eb727', 'TITLE' => do_lang_tempcode('VALIDATED'), 'CONTENT' => do_lang_tempcode('VALIDATED_SHORT'))));
        }
        $columns[] = do_lang_tempcode('ACTIONS');
        $header_row = results_field_title($columns, $sortables, 'sort', $sortable . ' ' . $sort_order);
        $table_rows = new Tempcode();
        foreach ($rows as $i => $row) {
            if (!$found_via_query) {
                if ($i < $start) {
                    continue;
                }
                if ($i > $max + $start) {
                    break;
                }
            }

            $page_path = $row['page_path'];

            $wrappable_page_link__tempcode = do_template('COMCODE_TELETYPE', array('_GUID' => 'bf4dbed562e189c84aa33c17d06c2791', 'CONTENT' => $row['page_link']));
            if (($row['order'] !== null) && ($row['order'] != ORDER_AUTOMATED_CRITERIA)) {
                $wrappable_page_link__tempcode->attach(escape_html(' (' . do_lang('ORDER') . ' #' . integer_format($row['order']) . ')'));
            }
            $page_hyperlink = hyperlink(build_url(array('page' => $row['page']), $row['zone']), escape_html($row['page_title']), false, false, protect_from_escaping($wrappable_page_link__tempcode));

            $zone_url = build_url(array('page' => ''), $row['zone']);
            $zone_name = empty($row['zone']) ? do_lang('_WELCOME') : $row['zone'];
            $zone_hyperlink = do_template('COMCODE_TELETYPE', array(
                '_GUID' => '46e1af60e09524c20fc62dd55cda1eb9',
                'CONTENT' => hyperlink($zone_url, $zone_name, false, true),
            ));

            $page__tempcode = do_template('COMCODE_TELETYPE', array(
                '_GUID' => '56e1af60e09524c20fc62dd55cda1eb9',
                'CONTENT' => preg_replace('#([' . URL_CONTENT_REGEXP . ']{22})#', '${1} ', escape_html($row['page'])),
            ));

            $parent_page = $row['parent_page'];

            if (is_null($row['submitter'])) {
                $username = do_lang('UNKNOWN');
            } else {
                $username = protect_from_escaping($GLOBALS['FORUM_DRIVER']->member_profile_hyperlink($row['submitter']));
            }

            if (is_null($row['add_date'])) {
                $add_date = get_timezoned_date(filectime(get_file_base() . '/sources/global.php'), false);
            } else {
                $add_date = get_timezoned_date($row['add_date'], false);
            }

            if (is_null($row['validated'])) {
                $validated = do_lang_tempcode('YES');
            } else {
                $validated = ($row['validated'] == 1) ? do_lang_tempcode('YES') : do_lang_tempcode('NO');
            }

            $edit_link = build_url(array('page' => '_SELF', 'type' => '_edit', 'page_link' => $row['page_link'], 'lang' => $lang), '_SELF');
            $clone_link = build_url(array('page' => '_SELF', 'type' => '_edit', 'page_link' => /*new page-link only has zone specified initially*/$row['zone'] . ':', 'restore_from_path' => $page_path, 'lang' => $lang), '_SELF');
            $actions = do_template('COMCODE_PAGE_EDIT_ACTIONS', array('_GUID' => '6cc8c492ba9ae4035c394fbe28a56c26', 'EDIT_URL' => $edit_link, 'CLONE_URL' => $clone_link));

            $display_map = array();
            $display_map[] = protect_from_escaping($page_hyperlink);
            $display_map[] = protect_from_escaping($zone_hyperlink);
            $display_map[] = protect_from_escaping($page__tempcode);
            if (!$show_helper_panel) {
                $display_map[] = $parent_page;
                $display_map[] = $username;
                $display_map[] = $add_date;
                $display_map[] = $validated;
            }
            $display_map[] = protect_from_escaping($actions);

            $table_rows->attach(results_entry($display_map, true));
        }
        if ($show_helper_panel) {
            $widths = array('33%', '19%', '28%', '20%');
        } else {
            $widths = null;
        }
        $table = results_table(do_lang('COMCODE_PAGES'), $start, 'start', $max, 'max', $max_rows, $header_row, $table_rows, $sortables, $sortable, $sort_order, 'sort', null, $widths, null, 8, 'fdgfdfdfdggfd', true);

        // Form
        if ($fields->is_empty()) {
            $extra = new Tempcode();
        } else {
            $map = array('page' => '_SELF', 'type' => '_edit', 'lang' => $lang);
            $post_url = build_url($map, '_SELF', null, false, true);

            $extra = do_template('FORM', array(
                '_GUID' => '52f88211619a877e5c6f85fd4d46a90e',
                'FIELDS' => $fields,
                'TEXT' => '',
                'URL' => $post_url,
                'GET' => true,
                'HIDDEN' => '',
                'SUBMIT_NAME' => $submit_name,
                'SUBMIT_ICON' => 'menu___generic_admin__add_one',
            ));
        }

        // Custom fields
        require_code('fields');
        $_links = manage_custom_fields_donext_link('comcode_page');
        $links = array();
        foreach ($_links as $_link) {
            $links[] = array(
                'LINK_IMAGE' => find_theme_image('icons/48x48/' . $_link[0]),
                'LINK_URL' => build_url(array('page' => $_link[1][0]) + $_link[1][1], $_link[1][2]),
                'LINK_TEXT' => $_link[2],
            );
        }

        // Render...

        $post_url = build_url(array('page' => '_SELF'), '_SELF');
        $hidden = build_keep_post_fields(array('filter'));

        $tpl = do_template('COMCODE_PAGE_MANAGE_SCREEN', array(
            '_GUID' => 'eba3e03c65d96530e3a42d600f90ccd8',
            'TITLE' => $this->title,
            'TEXT' => $text,
            'TABLE' => $table,
            'FIELDS' => $fields,
            'POST_URL' => $post_url,
            'HIDDEN' => $hidden,
            'SUBMIT_NAME' => $submit_name,
            'LINKS' => $links,
            'SUB_TITLE' => $fields->is_empty() ? new Tempcode() : do_lang_tempcode('COMCODE_PAGE_ADD'),
            'EXTRA' => $extra,
            'FILTER' => ($filter === null) ? '' : $filter,
        ));

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

    /**
     * The UI to edit a page.
     *
     * @return Tempcode The UI
     */
    public function _edit()
    {
        require_code('input_filter_2');
        modsecurity_workaround_enable();

        require_code('form_templates');

        $page_link = $this->page_link;
        $zone = $this->zone;
        $file = $this->file;

        if (($zone != '') && (!file_exists(get_file_base() . '/' . $zone . (($zone == '') ? '' : '/') . 'pages'))) {
            warn_exit(do_lang_tempcode('NO_SUCH_ZONE'));
        }

        if ((stripos(PHP_OS, 'WIN') === 0) && (version_compare(PHP_VERSION, '7.2', '<'))) {
            // Older versions of PHP on Windows cannot handle utf-8 filenames
            require_code('character_sets');
            $file = transliterate_string($file);
        }

        if (strlen($file) > 80) {
            warn_exit(do_lang_tempcode('BAD_CODENAME'));
        }

        require_code('type_sanitisation');
        if ((!is_alphanumeric($file)) || (strpos($file, '-') !== false && strpos($file, '_') !== false)/*can't have both*/) {
            warn_exit(do_lang_tempcode('BAD_CODENAME'));
        }

        $lang = choose_language(get_screen_title(($file == '') ? 'COMCODE_PAGE_ADD' : 'COMCODE_PAGE_EDIT'), true);
        if (is_object($lang)) {
            return $lang;
        }

        $resource_owner = $GLOBALS['SITE_DB']->query_select_value_if_there('comcode_pages', 'p_submitter', array('the_zone' => $zone, 'the_page' => $file));
        if (is_null($resource_owner)) { // Add
            if (!has_add_comcode_page_permission($zone)) {
                access_denied('ADD_COMCODE_PAGE');
            }
        } else { // Edit
            if (!has_edit_comcode_page_permission($zone, $file, $resource_owner)) {
                access_denied('EDIT_COMCODE_PAGE');
            }
        }

        list($file_base, $file_path) = find_comcode_page($lang, $file, $zone);

        // Check no redirects in our way
        if (addon_installed('redirects_editor')) {
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('redirects', 'r_to_zone', array('r_from_page' => $file, 'r_from_zone' => $zone));
            if (!is_null($test)) {
                $redirect_url = build_url(array('page' => 'admin_redirects'), get_module_zone('admin_redirects'));
                attach_message(do_lang_tempcode('BLOCKING_REDIRECT_IN_PLACE', escape_html($redirect_url->evaluate())), 'notice');
            }
        }

        if ($file == '') {
            url_default_parameters__enable();
        }

        if ($file != '') {
            check_page_name($zone, $file);
        }

        // Default file contents
        $contents = post_param_string('new', '');
        $parsed = null;
        if ($contents == '') {
            if (is_file($file_base . '/' . $file_path)) {
                $contents = cms_file_get_contents_safe($file_base . '/' . $file_path);

                if (strpos($file_path, '_custom/') === false) {
                    global $LANG_FILTER_OB;
                    $contents = $LANG_FILTER_OB->compile_time(null, $contents, $lang);
                }

                $comcode_page_rows = $GLOBALS['SITE_DB']->query_select('cached_comcode_pages', array('*'), array('the_zone' => $zone, 'the_page' => $file), '', 1);
                if (array_key_exists(0, $comcode_page_rows)) {
                    $parsed = get_translated_tempcode('cached_comcode_pages', $comcode_page_rows[0], 'string_index', null, $lang);
                }

                $new = false;
            } else {
                $template_name = get_param_string('page_template', $file);
                $contents = get_template_contents($template_name);

                $new = true;
            }

            if ($new) {
                if ((strpos($contents, '[title') === false) && (substr($file, 0, 6) != 'panel_') && (substr($file, 0, 1) != '_')) {
                    $contents = '[title]' . titleify($file) . '[/title]' . "\n\n" . $contents;

                    if ((get_option('is_on_comcode_page_children') == '1') && (has_privilege(get_member(), 'comcode_dangerous'))) {
                        $contents .= "\n\n" . '[block]main_comcode_page_children[/block]';
                    }
                }
            }
        } else {
            $new = false;
        }

        if (addon_installed('actionlog')) {
            require_code('revisions_engine_files');
            $revision_engine = new RevisionEngineFiles();
            $revision_loaded = mixed();
            $revisions = $revision_engine->ui_revision_undoer($zone . (($zone == '') ? '' : '/') . 'pages/comcode_custom/' . $lang, $file, 'txt', 'COMCODE_PAGE_EDIT', $contents, $revision_loaded);
            if ($revision_loaded) {
                $parsed = null;
            }
        } else {
            $revisions = new Tempcode();
        }

        // ---

        $post_url = build_url(array('page' => '_SELF', 'type' => '__edit'), '_SELF');

        if ((addon_installed('page_management')) && (has_actual_page_access(get_member(), 'admin_sitemap'))) {
            $delete_url = build_url(array('page' => 'admin_sitemap', 'type' => '_delete', 'page__' . $file => 1, 'zone' => $zone), get_module_zone('admin_sitemap'));
        } else {
            $delete_url = new Tempcode();
        }

        $hidden_fields = new Tempcode();
        $fields = new Tempcode();
        $fields2 = new Tempcode();

        if (addon_installed('page_management')) {
            if (has_actual_page_access(get_member(), 'admin_sitemap')) {
                $fields->attach(form_input_codename(do_lang_tempcode('CODENAME'), do_lang_tempcode('DESCRIPTION_PAGE_NAME'), 'title', $file, true));
            }
        }

        $rows = $GLOBALS['SITE_DB']->query_select('comcode_pages', array('*'), array('the_zone' => $zone, 'the_page' => $file), '', 1);
        if (array_key_exists(0, $rows)) {
            // Existing
            $validated = $rows[0]['p_validated'] == 1;
            $parent_page = $rows[0]['p_parent_page'];
            $show_as_edit = $rows[0]['p_show_as_edit'] == 1;
            $owner = $rows[0]['p_submitter'];
            $order = $rows[0]['p_order'];
        } else {
            // New or raw .txt not processed into DB yet
            $validated = true;
            $parent_page = get_param_string('parent_page', '');
            $show_as_edit = false;
            $owner = get_member();
            $order = $GLOBALS['SITE_DB']->query_select_value_if_there('comcode_pages', 'MAX(p_order)');
            if (is_null($order)) {
                $order = -1;
            }
            $order++;
        }

        if (!$validated) {
            $validated = (get_param_integer('validated', 0) == 1);
        }
        if (has_bypass_validation_comcode_page_permission($zone)) {
            if (addon_installed('unvalidated')) {
                $fields2->attach(form_input_tick(do_lang_tempcode('VALIDATED'), do_lang_tempcode($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member()) ? 'DESCRIPTION_VALIDATED_SIMPLE' : 'DESCRIPTION_VALIDATED', 'comcode_page'), 'validated', $validated));
            }
        }

        if (!$new) {
            if ($delete_url->is_empty()) {
                $fields2->attach(form_input_tick(do_lang_tempcode('DELETE'), do_lang_tempcode('DESCRIPTION_DELETE'), 'delete', false));
            }
        }

        if (get_option('is_on_comcode_page_children') == '1') {
            $_pages = find_all_pages_wrap($zone);
            ksort($_pages);
            $pages = form_input_list_entry('', false, do_lang_tempcode('NA_EM'));
            foreach (array_keys($_pages) as $page) {
                if (!is_string($page)) {
                    $page = strval($page);
                }
                if ($page != $file) {
                    $pages->attach(form_input_list_entry($page, $parent_page == $page));
                }
            }

            $fields2->attach(form_input_list(do_lang_tempcode('PARENT_PAGE'), do_lang_tempcode('DESCRIPTION_PARENT_PAGE'), 'parent_page', $pages, null, false, false));
        }

        $fields2->attach(form_input_tick(do_lang_tempcode('SHOW_AS_EDITED'), do_lang_tempcode('DESCRIPTION_SHOW_AS_EDITED'), 'show_as_edit', $show_as_edit));

        $fields2->attach(get_order_field('comcode_page', 'zone', $order));

        require_code('fields');
        append_form_custom_fields('comcode_page', $zone . ':' . $file, $fields2, $hidden_fields);

        $meta_keywords = post_param_string('meta_keywords', '');
        $meta_description = post_param_string('meta_description', '');
        if (($meta_keywords == '') && ($meta_description == '')) {
            list($meta_keywords, $meta_description) = seo_meta_get_for('comcode_page', $zone . ':' . $file);
        }
        $fields2->attach(do_template('FORM_SCREEN_FIELD_SPACER', array(
            '_GUID' => 'a42341a9a2de532cecdcfbecaff00a0f',
            'TITLE' => do_lang_tempcode('SEO'),
            'SECTION_HIDDEN' => true,
            'HELP' => (get_option('show_docs') === '0') ? null : do_lang_tempcode('TUTORIAL_ON_THIS', get_tutorial_url('tut_seo')),
        )));
        $fields2->attach(form_input_line_multi(do_lang_tempcode('KEYWORDS'), do_lang_tempcode('DESCRIPTION_META_KEYWORDS'), 'meta_keywords[]', array_map('trim', explode(',', preg_replace('#,+#', ',', $meta_keywords))), 0));
        $fields2->attach(form_input_line(do_lang_tempcode('META_DESCRIPTION'), do_lang_tempcode('DESCRIPTION_META_DESCRIPTION'), 'meta_description', $meta_description, false));

        if (addon_installed('awards')) {
            require_code('awards');
            $fields2->attach(get_award_fields('comcode_page', $zone . ':' . $file));
        }

        $fields2->attach(metadata_get_fields('comcode_page', ($page_link == '') ? null : $page_link));

        if (addon_installed('content_reviews')) {
            require_code('content_reviews2');
            $fields2->attach(content_review_get_fields('comcode_page', ($page_link == '') ? null : $page_link));
        }

        require_code('permissions2');
        $fields2->attach(get_page_permissions_for_environment($zone, $file));

        $hidden_fields->attach(form_input_hidden('file', $file));
        $hidden_fields->attach(form_input_hidden('lang', $lang));
        $hidden_fields->attach(form_input_hidden('zone', $zone));
        $hidden_fields->attach(form_input_hidden('redirect', get_param_string('redirect', '')));

        $posting_form = get_posting_form(do_lang(($file == '') ? 'COMCODE_PAGE_ADD' : 'SAVE'), ($file == '') ? 'menu___generic_admin__add_one' : 'menu___generic_admin__edit_this', $contents, $post_url, $hidden_fields, $fields, do_lang_tempcode('COMCODE_PAGE'), '', $fields2, $parsed, null, null, false);

        if ($file == '') {
            url_default_parameters__disable();
        }

        $text = new Tempcode();
        if (addon_installed('points')) {
            $login_url = build_url(array('page' => 'login', 'type' => 'browse', 'redirect' => get_self_url(true, true)), get_module_zone('login'));
            $_login_url = escape_html($login_url->evaluate());
            if ((is_guest()) && ((get_forum_type() != 'cns') || (has_actual_page_access(get_member(), 'join')))) {
                $text->attach(paragraph(do_lang_tempcode('NOT_LOGGED_IN_NO_CREDIT', $_login_url)));
            }
        }

        list($warning_details, $ping_url) = handle_conflict_resolution($page_link);

        return do_template('COMCODE_PAGE_EDIT_SCREEN', array(
            '_GUID' => 'ec1d773684757f5bf6f39cf931555bf2',
            'NEW' => $new,
            'PING_URL' => $ping_url,
            'WARNING_DETAILS' => $warning_details,
            'TEXT' => $text,
            'TITLE' => $this->title,
            'DELETE_URL' => $delete_url,
            'ZONE' => $zone,
            'FILE' => $file,
            'POSTING_FORM' => $posting_form,
            'REVISIONS' => $revisions,
        ));
    }

    /**
     * The actualiser to edit a Comcode page.
     *
     * @return Tempcode The UI
     */
    public function __edit()
    {
        require_code('input_filter_2');
        modsecurity_workaround_enable();

        // Load up settings from the environments
        $file = filter_naughty(post_param_string('file'));
        $lang = filter_naughty(post_param_string('lang'));
        $zone = filter_naughty(post_param_string('zone'));
        if (addon_installed('page_management')) {
            $new_file = filter_naughty(has_actual_page_access(get_member(), 'admin_sitemap') ? post_param_string('title', $file) : $file);
        } else {
            $new_file = filter_naughty($file);
        }

        if ((stripos(PHP_OS, 'WIN') === 0) && (version_compare(PHP_VERSION, '7.2', '<'))) {
            // Older versions of PHP on Windows cannot handle utf-8 filenames
            require_code('character_sets');
            $new_file = transliterate_string($new_file);
        }

        require_code('type_sanitisation');
        if ((!is_alphanumeric($new_file)) || (strpos($new_file, '-') !== false && strpos($new_file, '_') !== false)/*can't have both*/) {
            warn_exit(do_lang_tempcode('BAD_CODENAME'));
        }

        if ($file == '') {
            $file = $new_file;
        }

        $validated = post_param_integer('validated', 0);
        if (!addon_installed('unvalidated')) {
            $validated = 1;
        }
        require_code('antispam');
        inject_action_spamcheck();
        if (!has_bypass_validation_comcode_page_permission($zone)) {
            $validated = 0;
        }
        $parent_page = post_param_string('parent_page', '');
        $order = post_param_order_field();
        $show_as_edit = post_param_integer('show_as_edit', 0);
        $text_raw = post_param_string('post');
        $metadata = actual_metadata_get_fields('comcode_page', $zone . ':' . $file, null, $new_file);

        // Handle attachments
        require_code('attachments2');
        $_text = do_comcode_attachments($text_raw, 'comcode_page', $zone . ':' . $file);
        $text = $_text['comcode'];

        // Some general CRUD maintenance that we don't do within the save_comcode_page function
        $resource_owner = $GLOBALS['SITE_DB']->query_select_value_if_there('comcode_pages', 'p_submitter', array('the_zone' => $zone, 'the_page' => $file));
        if (is_null($resource_owner)) { // Add
            if (!has_add_comcode_page_permission($zone)) {
                access_denied('ADD_COMCODE_PAGE');
            }

            require_code('submit');
            give_submit_points('COMCODE_PAGE_ADD');

            require_code('member_mentions');
            dispatch_member_mention_notifications('comcode_page', $zone . ':' . $file, $resource_owner);
        } else { // Edit
            if (!has_edit_comcode_page_permission($zone, $file, $resource_owner)) {
                access_denied('EDIT_COMCODE_PAGE');
            }

            require_code('submit');
            $just_validated = (!content_validated('comcode_page', $zone . ':' . $file)) && ($validated == 1);
            if ($just_validated) {
                send_content_validated_notification('comcode_page', $zone . ':' . $file);
            }
        }
        require_code('permissions2');
        set_page_permissions_from_environment($zone, $file);
        if (addon_installed('awards')) {
            require_code('awards');
            handle_award_setting('comcode_page', $zone . ':' . $new_file);
        }
        if (addon_installed('content_reviews')) {
            require_code('content_reviews2');
            content_review_set('comcode_page', $zone . ':' . $new_file, $zone . ':' . $file);
        }

        // Main save function
        $path = save_comcode_page($zone, $new_file, $lang, $text, $validated, $parent_page, $order, $metadata['add_time'], $metadata['edit_time'], $show_as_edit, $metadata['submitter'], $file, post_param_string('meta_keywords', ''), post_param_string('meta_description', ''));

        // Deleting?
        if (post_param_integer('delete', 0) == 1) {
            check_delete_permission('high', $resource_owner);
            unlink(get_custom_file_base() . '/' . $path);
            sync_file(get_custom_file_base() . '/' . $path);

            // Delete custom fields
            require_code('fields');
            delete_form_custom_fields('comcode_page', $zone . ':' . $file);
        } else {
            // Save custom fields
            require_code('fields');
            save_form_custom_fields('comcode_page', $zone . ':' . $new_file, $zone . ':' . $file);
        }

        // Look for bad title semantics
        $_text['html'] = $_text['tempcode']->evaluate();
        if ((substr($file, 0, 1) != '_') && (substr($file, 0, 6) != 'panel_') && (trim($_text['html']) != '')) {
            if ((strpos($_text['html'], '<h1') === false) && (strpos($_text['comcode'], '[title]') === false) && (strpos($_text['comcode'], '[title="1"]') === false)) {
                attach_message(do_lang_tempcode('NO_LEVEL_1_HEADERS'), 'notice');
            }
            $matches = array();
            if ((strpos($_text['html'], '<h2') === false) && (preg_match_all('#\n\[(b|font|size)\][^\.]+\[/(b|font|size)\]\r?\n#', $_text['comcode'], $matches) >= 2)) {
                attach_message(do_lang_tempcode('NO_LEVEL_2_HEADERS'), 'inform');
            }
        }

        // Look for conflicting page names
        $existing_page = __request_page($new_file, $zone);
        if (($existing_page !== false) && (strpos($existing_page[0], 'COMCODE') === false)) {
            if ($existing_page[0] == 'REDIRECT') {
                attach_message(do_lang_tempcode('CONFLICTING_PAGE_NAME_REDIRECT'), 'warn');
            } else {
                attach_message(do_lang_tempcode('CONFLICTING_PAGE_NAME_MODULE', escape_html($existing_page[count($existing_page) - 1])), 'warn');
            }
        }

        // Messaging to user
        if ($validated == 0) {
            require_code('submit');
            $edit_url = build_url(array('page' => '_SELF', 'type' => '_edit', 'page_link' => $zone . ':' . $new_file), '_SELF', null, false, false, true);
            if (addon_installed('unvalidated')) {
                send_validation_request('COMCODE_PAGE_EDIT', 'comcode_pages', true, $zone . ':' . $new_file, $edit_url);
            }
        }
        $completion_text = ($validated == 0) ? do_lang_tempcode('SUBMIT_UNVALIDATED', 'comcode_page') : do_lang_tempcode('SUCCESS');
        if ($new_file == $file) {
            $url = post_param_string('redirect', '');
        } else {
            $url = '';
        }
        if ($url != '') {
            return redirect_screen($this->title, $url, $completion_text);
        }
        return $this->do_next_manager($this->title, $new_file, $zone, $completion_text);
    }

    /**
     * Generate page sitemap.
     *
     * @return Tempcode The UI
     */
    public function generate_page_sitemap()
    {
        require_css('sitemap_editor');

        require_code('type_sanitisation');

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

        $zone = get_param_string('filter', null);

        $total_known_pages = $GLOBALS['SITE_DB']->query_select_value('comcode_pages', 'COUNT(*)');
        if (
            ($total_known_pages < 300) &&
            ($GLOBALS['SITE_DB']->query_select_value_if_there('comcode_pages c LEFT JOIN ' . get_table_prefix() . 'cached_comcode_pages a ON c.the_page=a.the_page AND c.the_zone=a.the_zone', 'c.the_page', array('a.the_page' => null)) !== null)
        ) {
            $____pages = $GLOBALS['SITE_DB']->query_select('comcode_pages', array('the_zone', 'the_page', 'p_parent_page', 'p_validated'));
            $___pages = array();
            foreach ($____pages as $____page) {
                $___pages[$____page['the_zone'] . ':' . $____page['the_page']] = $____page;
            }

            $files_list = $this->get_comcode_files_list_disk_search(user_lang(), null, false);
            $__pages = array();
            foreach ($files_list as $page_link => $path_bits) {
                list($_zone, $__page) = explode(':', $page_link, 2);
                if (!is_string($__page)) {
                    $__page = strval($__page);
                }

                $__pages[] = array(
                    'the_zone' => $_zone,
                    'the_page' => $__page,
                    'p_parent_page' => isset($___pages[$page_link]) ? $___pages[$page_link]['p_parent_page'] : '',
                    'p_validated' => isset($___pages[$page_link]) ? $___pages[$page_link]['p_validated'] : 1,
                    'cc_page_title' => get_comcode_page_title_from_disk($path_bits[0]),
                    'string_index' => file_get_contents($path_bits[0]),
                );
            }
        } else {
            $__pages = $GLOBALS['SITE_DB']->query_select(
                'comcode_pages a LEFT JOIN ' . get_table_prefix() . 'cached_comcode_pages b ON a.the_zone=b.the_zone AND a.the_page=b.the_page',
                array('a.the_zone', 'a.the_page', 'a.p_parent_page', 'a.p_validated', 'b.cc_page_title', 'b.string_index'),
                null,
                'ORDER BY the_zone,the_page'
            );
        }
        $_pages = array();
        foreach ($__pages as $i => $_page) {
            if (substr($_page['the_page'], 0, 6) == 'panel_') {
                unset($__pages[$i]);
                continue;
            }
            if (substr($_page['the_page'], 0, 1) == '_') {
                unset($__pages[$i]);
                continue;
            }
            if (!is_alphanumeric($_page['the_page'])) { // e.g. 404 page
                unset($__pages[$i]);
                continue;
            }
            if (!is_alphanumeric($_page['the_zone'])) {
                unset($__pages[$i]);
                continue;
            }

            $_pages[] = $_page;
        }
        $pages = array();
        foreach ($_pages as $_page) {
            if ((!is_null($zone)) && ($_page['the_zone'] != $zone)) {
                continue;
            }

            if (!isset($pages[$_page['p_parent_page']])) {
                $pages[$_page['p_parent_page']] = array();
            }
            $pages[$_page['p_parent_page']][] = $_page;
        }

        $_zones = find_all_zones(false, true, true);
        $zones = array();
        foreach ($_zones as $_zone) {
            if (!is_alphanumeric($_zone[0])) {
                continue;
            }

            // Check has something in the zone
            $ok = false;
            foreach ($__pages as $_page) {
                if ($_zone[0] == $_page['the_zone']) {
                    $ok = true;
                    break;
                }
            }
            if (!$ok) {
                continue;
            }

            $zones[$_zone[0]] = $_zone[1];
        }

        // Sort zones
        uasort($zones, 'strnatcasecmp');
        if (isset($zones[''])) { // Move welcome zone to start of list
            $zones = array_merge(array('' => $zones['']), $zones);
        }

        $page_structure = $this->organise_page_tree($pages);

        return do_template('GENERATE_PAGE_SITEMAP_SCREEN', array('_GUID' => 'b07b07bfca5d4191e99671040b70ed51', 'TITLE' => $this->title, 'ZONES' => $zones, 'PAGE_STRUCTURE' => $page_structure));
    }

    /**
     * Get the page sitemap tree structure.
     *
     * @param  array $pages An array of pages
     * @param  ID_TEXT $under The page we are looking under
     * @return Tempcode The structure
     */
    public function organise_page_tree(&$pages, $under = '')
    {
        static $todo_checks = null;
        static $no_validation_support = null;
        static $menu_branches_by_url = null;
        static $menu_branches_by_id = null;
        static $user_lang = null;
        if ($todo_checks === null) {
            $_todo_checks = cms_mb_strtolower(do_lang('UNDER_CONSTRUCTION_MARKERS'));
            $todo_checks = ($_todo_checks == '') ? array() : explode('|', $_todo_checks);

            $no_validation_support = !addon_installed('unvalidated');

            $zone_start_pages = collapse_2d_complexity('zone_name', 'zone_default_page', $GLOBALS['SITE_DB']->query_select('zones', array('zone_name', 'zone_default_page')));

            $menu_branches = $GLOBALS['SITE_DB']->query_select('menu_items', array('id', 'i_menu', 'i_parent', 'i_caption', 'i_url'), null, 'ORDER BY i_menu');
            $menu_branches_by_url = array();
            foreach ($menu_branches as $menu_branch) {
                $matches = array();
                if (preg_match('#^(\w*):$#', $menu_branch['i_url'], $matches) != 0) {
                    if (isset($zone_start_pages[$matches[1]])) {
                        $menu_branch['i_url'] .= $zone_start_pages[$matches[1]];
                    }
                }

                if (!isset($menu_branches_by_url[$menu_branch['i_url']])) {
                    $menu_branches_by_url[$menu_branch['i_url']] = array();
                }
                $menu_branches_by_url[$menu_branch['i_url']][] = $menu_branch;
            }
            $menu_branches_by_id = list_to_map('id', $menu_branches);

            $user_lang = user_lang();
        }

        $page_structure = array();
        foreach (isset($pages[$under]) ? $pages[$under] : array() as $page) {
            $todo = false;
            if ($page['string_index'] !== null) {
                $page_contents = is_string($page['string_index']) ? $page['string_index'] : get_translated_text($page['string_index']);
            } else {
                $located = _request_page($page['the_page'], $page['the_zone']);
                $_zone = $located[count($located) - 1];
                if ($located !== false && $located[0] != 'REDIRECT' && isset($located[4])) {
                    $page_path = get_custom_file_base() . (($_zone == '') ? '' : '/') . $_zone;
                    if (!is_file($page_path)) {
                        $page_path = get_file_base() . (($_zone == '') ? '' : '/') . $_zone;
                    }
                    $page_contents = file_get_contents($page_path);
                } else {
                    $page_contents = '';
                }
            }
            if (!empty($page_contents)) {
                if ($user_lang == 'EN') {
                    $page_contents_lower = $page_contents; // HACKHACK: For performance we can assume English only requires ASCII characters for this check
                } else {
                    $page_contents_lower = cms_mb_strtolower($page_contents);
                }

                foreach ($todo_checks as $todo_check) {
                    if (strpos($page_contents_lower, $todo_check) !== false) {
                        $todo = true;
                    }
                }
                if (strpos($page_contents, 'TODO') !== false) {
                    $todo = true;
                }
            }

            $page_link = $page['the_zone'] . ':' . $page['the_page'];

            $menu_paths = array();
            foreach (isset($menu_branches_by_url[$page_link]) ? $menu_branches_by_url[$page_link] : array() as $menu_branch) {
                $menu_path_components = array();
                $menu_branch_temp = $menu_branch;
                do {
                    $menu_path_components[] = get_translated_text($menu_branch_temp['i_caption']);
                    $parent = $menu_branch_temp['i_parent'];
                    if (!is_null($parent)) {
                        $menu_branch_temp = $menu_branches_by_id[$parent];
                    }
                } while (!is_null($parent));

                $menu_url = build_url(array('page' => 'admin_menus', 'type' => 'edit', 'id' => $menu_branch['i_menu']), get_module_zone('admin_menus'));
                $menu_paths[] = array(
                    'MENU' => $menu_branch['i_menu'],
                    'MENU_URL' => $menu_url,
                    'MENU_PATH_COMPONENTS' => array_reverse($menu_path_components),
                );
            }

            $edit_url = build_url(array('page' => '_SELF', 'type' => '_edit', 'page_link' => $page_link), '_SELF');

            $page_structure[] = array(
                'EDIT_URL' => $edit_url,
                'ZONE_NAME' => $page['the_zone'],
                'PAGE_NAME' => $page['the_page'],
                'PAGE_TITLE' => ($page['cc_page_title'] === null) ? '' : (is_string($page['cc_page_title']) ? $page['cc_page_title'] : get_translated_text($page['cc_page_title'])),
                'VALIDATED' => $no_validation_support || ($page['p_validated'] == 1),
                'TODO' => $todo,
                'MENU_PATHS' => $menu_paths,
                'CHILDREN' => $this->organise_page_tree($pages, $page['the_page']),
            );
        }

        return do_template('GENERATE_PAGE_SITEMAP', array('_GUID' => 'f92876ca45010873c71a9fea574d17c1', 'PAGE_STRUCTURE' => $page_structure));
    }
}
