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

/**
 * Module page class.
 */
class Module_admin_themes
{
    /**
     * 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'] = true;
        $info['update_require_upgrade'] = true;
        return $info;
    }

    /**
     * Uninstall the module.
     */
    public function uninstall()
    {
        $GLOBALS['SITE_DB']->drop_table_if_exists('theme_images');

        require_code('files');
        $langs = find_all_langs(true);
        foreach (array_keys($langs) as $lang) {
            deldir_contents(get_custom_file_base() . '/themes/default/templates_cached/' . $lang, true);
        }
        // *_custom purposely left
    }

    /**
     * Install the module.
     *
     * @param  ?integer $upgrade_from What version we're upgrading from (null: new install)
     * @param  ?integer $upgrade_from_hack What hack version we're upgrading from (null: new-install/not-upgrading-from-a-hacked-version)
     */
    public function install($upgrade_from = null, $upgrade_from_hack = null)
    {
        require_code('themes2');

        $themes = find_all_themes(); // Find all images for all themes

        if (is_null($upgrade_from)) {
            $GLOBALS['SITE_DB']->create_table('theme_images', array(
                'id' => '*SHORT_TEXT',
                'theme' => '*MINIID_TEXT',
                'path' => 'URLPATH',
                'lang' => '*LANGUAGE_NAME'
            ), false, false, true);
            $GLOBALS['SITE_DB']->create_index('theme_images', 'theme', array('theme', 'lang'));
        }
    }

    /**
     * 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_THEMES', 'menu/adminzone/style/themes/themes'),
            'add_theme' => array('ADD_THEME', 'menu/_generic_admin/add_one'),
            'tempcode_tester' => array('TEMPCODE_TESTER', 'menu/_generic_admin/tool'),
        );

        if ($support_crosslinks) {
            if (addon_installed('themewizard')) {
                $ret['_SEARCH:admin_themewizard:browse'] = array('THEMEWIZARD', 'menu/adminzone/style/themes/themewizard');
            }
        }

        return $ret;
    }

    public $title;
    public $id;
    public $theme;
    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('themes');

        if ($type != 'screen_previews' && $type != 'screen_preview') {
            set_helper_panel_tutorial('tut_themes');
        }

        if ($type == 'browse') {
            set_helper_panel_text(comcode_lang_string('DOC_THEMES'));

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

        if ($type == 'choose_css') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));
            breadcrumb_set_self(do_lang_tempcode('CHOOSE_CSS'));

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

        if ($type == 'edit_css') {
            require_code('input_filter_2');
            modsecurity_workaround_enable();

            set_helper_panel_text(comcode_lang_string('DOC_CSS'));
            set_helper_panel_tutorial('tut_markup');

            $theme = get_param_string('theme', '');
            if ($theme == '') {
                breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));
                breadcrumb_set_self(do_lang_tempcode('CHOOSE'));
            } else {
                breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES')), array('_SELF:_SELF:choose_css:theme=' . $theme, do_lang_tempcode('CHOOSE_CSS'))));
            }

            $file = filter_naughty(get_param_string('file', 'global.css'));

            $this->title = get_screen_title('_EDIT_CSS', true, array(escape_html($file)));

            $this->file = $file;

            $file = preg_replace('#\.\d+#', '', $file);
            set_short_title($file);
        }

        if ($type == '_edit_css') {
            require_code('input_filter_2');
            modsecurity_workaround_enable();

            $theme = post_param_string('theme');
            $file = post_param_string('file');

            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES')), array('_SELF:_SELF:edit_css:theme=' . $theme, do_lang_tempcode('CHOOSE_CSS')), array('_SELF:_SELF:_edit_css:file=' . $file, do_lang_tempcode('EDIT_CSS'))));
            breadcrumb_set_self(do_lang_tempcode('DONE'));

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

        if ($type == 'edit_templates') {
            set_helper_panel_text(comcode_lang_string('DOC_TEMPLATES'));
            set_helper_panel_tutorial('tut_markup');

            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));
            breadcrumb_set_self(do_lang_tempcode('CHOOSE_TEMPLATES'));

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

        if ($type == '_edit_templates') {
            require_code('input_filter_2');
            modsecurity_workaround_enable();

            set_helper_panel_text(comcode_lang_string('DOC_MARKUP'));
            set_helper_panel_tutorial('tut_themes');

            $theme = get_param_string('theme');
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES')), array('_SELF:_SELF:edit_templates:theme=' . $theme, do_lang_tempcode('CHOOSE_TEMPLATES'))));

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

        if ($type == '__edit_templates') {
            require_code('input_filter_2');
            modsecurity_workaround_enable();

            $theme = post_param_string('theme');
            $file = '';
            foreach (array_keys($_REQUEST) as $_i) {
                if (preg_match('#^f(\d+)file$#', $_i) != 0) {
                    $file = either_param_string($_i);
                }
            }
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES')), array('_SELF:_SELF:edit_templates:theme=' . $theme, do_lang_tempcode('CHOOSE_TEMPLATES')), array('_SELF:_SELF:_edit_templates:theme=' . $theme . ':file=' . $file, do_lang_tempcode('EDIT_TEMPLATES'))));
            breadcrumb_set_self(do_lang_tempcode('DONE'));

            $this->theme = $theme;

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

        if ($type == 'manage_images') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));
            breadcrumb_set_self(do_lang_tempcode('CHOOSE_THEME_IMAGE'));

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

        if ($type == 'edit_image') {
            $url = get_param_string('url', '');
            if ($url != '') {
                $url = preg_replace('#\.pagespeed\..*$#', '', $url); // Support for working around https://developers.google.com/speed/docs/mod_pagespeed/filter-cache-extend

                $theme = get_param_string('theme', ''); // Editing like this happens in the theme the user is using
                if ($theme == '') {
                    $theme = $GLOBALS['FORUM_DRIVER']->get_theme('');
                }
                if (substr($url, 0, strlen(get_base_url())) == get_base_url()) {
                    $url = substr($url, strlen(get_base_url()));
                }
                $pos = strpos($url, 'themes/');
                if ($pos === false) {
                    warn_exit(do_lang_tempcode('NOT_THEME_IMAGE'));
                }
                $path = substr($url, $pos);
                $id = $GLOBALS['SITE_DB']->query_select_value_if_there('theme_images', 'id', array('path' => $path, 'theme' => $theme));
                if (is_null($id)) {
                    warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
                }
            } else {
                $id = get_param_string('id');
                $theme = get_param_string('theme', $GLOBALS['FORUM_DRIVER']->get_theme(''));
            }

            set_short_title($id);

            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES')), array('_SELF:_SELF:manage_images:theme=' . $theme, do_lang_tempcode('CHOOSE_THEME_IMAGE'))));

            $this->title = get_screen_title('EDIT_THEME_IMAGE');

            $this->id = $id;
            $this->theme = $theme;
        }

        if ($type == '_edit_image') {
            $theme = post_param_string('theme');
            $id = post_param_string('id');
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES')), array('_SELF:_SELF:manage_images:theme=' . $theme, do_lang_tempcode('CHOOSE_THEME_IMAGE')), array('_SELF:_SELF:edit_theme_image:' . $id, do_lang_tempcode('EDIT_THEME_IMAGE'))));
            breadcrumb_set_self(do_lang_tempcode('DONE'));

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

        if ($type == 'add_image') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));

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

        if ($type == '_add_image') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES')), array('_SELF:_SELF:add_image:theme=' . post_param_string('theme'), do_lang_tempcode('ADD_THEME_IMAGE'))));
            breadcrumb_set_self(do_lang_tempcode('DONE'));

            breadcrumb_set_self(do_lang_tempcode('DONE'));

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

        if ($type == 'add_theme') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));

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

        if ($type == '_add_theme') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));
            breadcrumb_set_self(do_lang_tempcode('DONE'));

            breadcrumb_set_self(do_lang_tempcode('DONE'));

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

        if ($type == 'edit_theme') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));

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

        if ($type == '_edit_theme') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));
            breadcrumb_set_self(do_lang_tempcode('DONE'));

            breadcrumb_set_self(do_lang_tempcode('DONE'));

            if (post_param_integer('delete', 0) == 1) {
                $this->title = get_screen_title('DELETE_THEME');
            } elseif (post_param_integer('copy', 0) == 1) {
                $this->title = get_screen_title('COPY_THEME');
            } else {
                $this->title = get_screen_title('EDIT_THEME');
            }
        }

        if ($type == 'screen_previews') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));

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

        if ($type == 'screen_preview') {
            $function = get_param_string('function');

            //get_screen_title('SCREEN_PREVIEW', true, array(escape_html($function))); // Affects breadcrumbs etc
            get_screen_title($function, false); // Affects breadcrumbs etc
            breadcrumb_set_parents(array(array('_SELF:_SELF:screen_previews', do_lang_tempcode('SCREEN_PREVIEWS'))));
        }

        if ($type == 'tempcode_tester') {
            $this->title = get_screen_title('TEMPCODE_TESTER');

            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('MANAGE_THEMES'))));
        }

        return null;
    }

    /**
     * Execute the module.
     *
     * @return Tempcode The result of execution.
     */
    public function run()
    {
        require_lang('themes');
        require_code('view_modes');
        require_code('themes2');
        require_lang('zones');
        require_code('files');
        require_code('images');

        require_css('themes_editor');

        require_javascript('themeing');

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

        if ($type == 'browse') {
            return $this->manage_themes();
        }
        if ($type == 'choose_css') {
            return $this->choose_css();
        }
        if ($type == 'edit_css') {
            return $this->edit_css();
        }
        if ($type == '_edit_css') {
            return $this->_edit_css();
        }
        if ($type == 'edit_templates') {
            return $this->edit_templates();
        }
        if ($type == '_edit_templates') {
            return $this->_edit_templates();
        }
        if ($type == '__edit_templates') {
            return $this->__edit_templates();
        }
        if ($type == 'manage_images') {
            return $this->manage_images();
        }
        if ($type == 'edit_image') {
            return $this->edit_image();
        }
        if ($type == '_edit_image') {
            return $this->_edit_image();
        }
        if ($type == 'add_image') {
            return $this->add_image();
        }
        if ($type == '_add_image') {
            return $this->_add_image();
        }
        if ($type == 'add_theme') {
            return $this->add_theme();
        }
        if ($type == '_add_theme') {
            return $this->_add_theme();
        }
        if ($type == 'edit_theme') {
            return $this->edit_theme();
        }
        if ($type == '_edit_theme') {
            return $this->_edit_theme();
        }
        if ($type == 'screen_previews') {
            return $this->list_screen_previews();
        }
        if ($type == 'screen_preview') {
            return $this->view_screen_preview();
        }
        if ($type == 'tempcode_tester') {
            return $this->tempcode_tester();
        }

        return new Tempcode();
    }

    /**
     * The UI to manage themes.
     *
     * @return Tempcode The UI
     */
    public function manage_themes()
    {
        $_themes = find_all_themes(true);

        // Look through zones
        $zones = $GLOBALS['SITE_DB']->query_select('zones', array('*'), null, 'ORDER BY zone_title', 50/*reasonable limit; zone_title is sequential for default zones*/);
        $free_choices = 0;
        $zone_list_free_choices = new Tempcode();
        $no_themes_explicitly_set = true;
        foreach ($zones as $zone) {
            if (array_key_exists($zone['zone_theme'], $_themes)) {
                if (($zone['zone_name'] == '') && ($zone['zone_theme'] != '-1')) {
                    $no_themes_explicitly_set = false;
                }
            } else {
                if (!$zone_list_free_choices->is_empty()) {
                    $zone_list_free_choices->attach(do_lang_tempcode('LIST_SEP'));
                }
                $zone_list_free_choices->attach(($zone['zone_name'] == '') ? do_lang('_WELCOME') : $zone['zone_name']);

                $free_choices++;
            }
        }

        require_css('do_next');

        // Show all themes
        $site_default_theme = $GLOBALS['FORUM_DRIVER']->_get_theme(true);
        $themes = array();
        $theme_default_reason = do_lang_tempcode('DEFAULT_THEME_BY_DEFAULT', escape_html(get_default_theme_name()));
        foreach ($_themes as $theme => $details) {
            if (is_integer($theme)) {
                $theme = strval($theme);
            }

            $is_main_theme = false;

            // Get URLs
            $css_url = build_url(array('page' => '_SELF', 'type' => 'choose_css', 'theme' => $theme), '_SELF');
            $templates_url = build_url(array('page' => '_SELF', 'type' => 'edit_templates', 'theme' => $theme), '_SELF');
            $images_url = build_url(array('page' => '_SELF', 'type' => 'manage_images', 'theme' => $theme), '_SELF');
            $deletable = ($theme != 'default');
            $edit_url = build_url(array('page' => '_SELF', 'type' => 'edit_theme', 'theme' => $theme), '_SELF');
            $delete_url = build_url(array('page' => '_SELF', 'type' => 'delete_theme', 'theme' => $theme), '_SELF');
            $screen_preview_url = build_url(array('page' => '_SELF', 'type' => 'screen_previews', 'keep_theme' => $theme), '_SELF');

            // Theme date
            $date = filemtime(($theme == 'default' || $theme == 'admin') ? (get_file_base() . '/themes/default') : (get_custom_file_base() . '/themes/' . $theme));
            $_date = ($theme == 'default' || $theme == 'admin') ? do_lang_tempcode('NA_EM') : protect_from_escaping(escape_html(get_timezoned_date($date, false)));

            // Where the theme is used
            $zone_list = new Tempcode();
            if ($theme == $site_default_theme) {
                if ((count($zones) < 10) && (!is_cns_satellite_site()) && ($free_choices == 0)) {
                    $zone_list->attach($zone_list_free_choices); // Actually will do nothing, as $free_choices == 0
                } else {
                    $zone_list->attach(do_lang_tempcode('THEME_DEFAULT_FOR_SITE'));

                    if ($no_themes_explicitly_set) {
                        $is_main_theme = true;
                    }
                }

                // Why is this the site-default theme?
                if ($theme == preg_replace('#[^' . URL_CONTENT_REGEXP . ']#', '_', get_site_name())) {
                    $theme_default_reason = do_lang_tempcode('DEFAULT_THEME_BY_SITENAME');
                } elseif ($theme != 'default') {
                    $theme_default_reason = do_lang_tempcode('DEFAULT_THEME_BY_FORUM');
                }
            }
            foreach ($zones as $zone) {
                if ($zone['zone_theme'] == $theme) {
                    if ($zone['zone_name'] == '') {
                        $is_main_theme = true;
                    }

                    if ((get_option('collapse_user_zones') == '1') && ($zone['zone_name'] == 'site')) {
                        continue;
                    }

                    if (!$zone_list->is_empty()) {
                        $zone_list->attach(do_lang_tempcode('LIST_SEP'));
                    }
                    $zone_list->attach($zone['zone_name']);
                }
            }
            if (!$zone_list->is_empty()) {
                $theme_usage = do_lang_tempcode('THEME_USED_ON', $zone_list);
            } else {
                $theme_usage = new Tempcode();
            }

            // Render
            $seed = null;
            if (addon_installed('themewizard')) {
                require_code('themewizard');
                $seed = find_theme_seed($theme);
            }
            $themes[] = array(
                '_GUID' => 'c65c7f3f87d62ad425c7a104a6018840',
                'SEED' => $seed,
                'THEME_USAGE' => $theme_usage,
                'DATE' => $_date,
                'RAW_DATE' => strval($date),
                'NAME' => $theme,
                'DESCRIPTION' => $details['description'],
                'AUTHOR' => $details['author'],
                'TITLE' => $details['title'],
                'CSS_URL' => $css_url,
                'TEMPLATES_URL' => $templates_url,
                'IMAGES_URL' => $images_url,
                'DELETABLE' => $deletable,
                'EDIT_URL' => $edit_url,
                'DELETE_URL' => $delete_url,
                'SCREEN_PREVIEW_URL' => $screen_preview_url,
                'IS_MAIN_THEME' => $is_main_theme,
            );
        }

        $zones = find_all_zones(false, true);
        require_lang('zones');

        if ((count($zones) < 10) && (!is_cns_satellite_site()) && ($free_choices == 0)) {
            $theme_default_reason = new Tempcode(); // We don't need to know the reason really; don't over-complicate simple sites
        }

        return do_template('THEME_MANAGE_SCREEN', array('_GUID' => '1dc277f18562976f6a23facec56a98e8', 'TITLE' => $this->title, 'THEMES' => $themes, 'THEME_DEFAULT_REASON' => $theme_default_reason, 'ZONES' => $zones, 'HAS_FREE_CHOICES' => $free_choices != 0));
    }

    /**
     * Get standard form input fields for inputting a theme.
     *
     * @param  string $name The name of the theme
     * @param  string $title The theme title
     * @param  string $description The theme description
     * @param  ?string $author The theme author (null: current member)
     * @param  string $mobile_pages Comma-separated list mobile-supporting pages (blank: all do)
     * @param  BINARY $supports_wide Whether the theme supports 'wide' screens
     * @param  boolean $use_on_all_zones Whether to use this theme on all zones
     * @return Tempcode The fields
     */
    public function get_theme_fields($name = '', $title = '', $description = '', $author = null, $mobile_pages = '', $supports_wide = 1, $use_on_all_zones = false)
    {
        if (is_null($author)) {
            $author = $GLOBALS['FORUM_DRIVER']->get_username(get_member(), true);
        }

        require_code('form_templates');
        require_code('permissions2');

        $fields = new Tempcode();
        $site_default_theme = preg_replace('#[^' . URL_CONTENT_REGEXP . ']#', '_', get_site_name());
        $fields->attach(form_input_line(do_lang_tempcode('TITLE'), do_lang_tempcode('DESCRIPTION_TITLE'), 'title', $title, true));
        if ($name != 'default') {
            $fields->attach(form_input_codename(do_lang_tempcode('CODENAME'), do_lang_tempcode(file_exists(get_custom_file_base() . '/themes/' . $site_default_theme) ? 'DESCRIPTION_CODENAME_THEME' : 'DESCRIPTION_CODENAME_THEME_HELPER', escape_html($site_default_theme)), 'theme', $name, true));
        }

        $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => '6fc4708641814ddf5e99d0771abaa8f8', 'SECTION_HIDDEN' => true, 'TITLE' => do_lang_tempcode('ADVANCED'))));

        $fields->attach(form_input_line(do_lang_tempcode('DESCRIPTION'), do_lang_tempcode('DESCRIPTION_DESCRIPTION'), 'description', $description, false));
        $fields->attach(form_input_line(do_lang_tempcode('AUTHOR'), do_lang_tempcode('DESCRIPTION_AUTHOR_THEME', do_lang_tempcode('THEME')), 'author', $author, true));
        $fields->attach(form_input_tick(do_lang_tempcode('SUPPORTS_WIDE'), do_lang_tempcode('DESCRIPTION_SUPPORTS_WIDE'), 'supports_wide', $supports_wide == 1));
        $fields->attach(form_input_line(do_lang_tempcode('MOBILE_PAGES'), do_lang_tempcode('DESCRIPTION_MOBILE_PAGES'), 'mobile_pages', $mobile_pages, false));
        if ($name != 'default') {
            $fields->attach(get_category_permissions_for_environment('theme', $name, null, null, ($name == '')));
        }

        $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => '805d278a21d59eaa4568c2a77fbb5073', 'TITLE' => do_lang_tempcode('ACTIONS'))));

        $fields->attach(form_input_tick(do_lang_tempcode('USE_ON_ZONES'), do_lang_tempcode('DESCRIPTION_USE_ON_ZONES'), 'use_on_all', $use_on_all_zones));

        // Mapping
        if (method_exists($GLOBALS['FORUM_DRIVER'], 'get_skin_list')) {
            $map = file_exists(get_file_base() . '/themes/map.ini') ? better_parse_ini_file(get_file_base() . '/themes/map.ini') : array();
            $default_selection = array();
            $mapping = new Tempcode();
            $all_skins = $GLOBALS['FORUM_DRIVER']->get_skin_list();
            foreach ($map as $key => $val) {
                if ($val == $name) {
                    $default_selection[] = $key;
                }
            }
            foreach ($all_skins as $key) {
                $mapping->attach(form_input_list_entry($key, in_array($key, $default_selection)));
            }
            $fields->attach(form_input_multi_list(do_lang_tempcode('THEME_MAPPING'), do_lang_tempcode('DESCRIPTION_THEME_MAPPING'), 'mapping', $mapping));
        }

        return $fields;
    }

    /**
     * Common theme change saving for adding and editing themes.
     *
     * @param  ID_TEXT $theme The name of the theme
     */
    public function save_theme_changes($theme)
    {
        require_code('files');

        if (!file_exists((($theme == 'default' || $theme == 'admin') ? get_file_base() : get_custom_file_base()) . '/themes/' . filter_naughty($theme) . '/theme.ini')) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
        }

        if (post_param_integer('use_on_all', 0) == 1) {
            $GLOBALS['SITE_DB']->query('UPDATE ' . get_table_prefix() . 'zones SET zone_theme=\'' . db_escape_string($theme) . '\' WHERE ' . db_string_not_equal_to('zone_name', 'cms') . ' AND ' . db_string_not_equal_to('zone_name', 'adminzone'));

            attach_message(do_lang_tempcode('THEME_MADE_LIVE'), 'inform');
        }
        erase_persistent_cache();

        $before = better_parse_ini_file((($theme == 'default' || $theme == 'admin') ? get_file_base() : get_custom_file_base()) . '/themes/' . filter_naughty($theme) . '/theme.ini');
        $path = (($theme == 'default' || $theme == 'admin') ? get_file_base() : get_custom_file_base()) . '/themes/' . filter_naughty($theme) . '/theme.ini';
        $contents = '';
        $contents .= 'title=' . post_param_string('title') . "\n";
        $contents .= 'description=' . str_replace("\n", '\n', post_param_string('description')) . "\n";
        foreach ($before as $key => $val) {
            if (($key != 'title') && ($key != 'description') && ($key != 'author') && ($key != 'mobile_pages') && ($key != 'supports_wide')) {
                $contents .= $key . '=' . $val . "\n";
            }
        }
        $contents .= 'author=' . post_param_string('author') . "\n";
        $contents .= 'mobile_pages=' . post_param_string('mobile_pages') . "\n";
        $contents .= 'supports_wide=' . strval(post_param_integer('supports_wide', 0)) . "\n";
        cms_file_put_contents_safe($path, $contents, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);

        require_code('permissions2');
        set_category_permissions_from_environment('theme', $theme);

        $map = file_exists(get_file_base() . '/themes/map.ini') ? better_parse_ini_file(get_file_base() . '/themes/map.ini') : array();
        $new_map = array();
        foreach ($map as $key => $val) {
            if ($val != $theme) {
                $new_map[$key] = $val;
            }
        }
        if (array_key_exists('mapping', $_POST)) {
            foreach ($_POST['mapping'] as $val) {
                if (@get_magic_quotes_gpc()) {
                    $val = stripslashes($val);
                }
                $new_map[$val] = $theme;
            }
        }
        $path = get_file_base() . '/themes/map.ini';
        $contents = '';
        foreach ($new_map as $key => $val) {
            $contents .= $key . '=' . $val . "\n";
        }
        cms_file_put_contents_safe($path, $contents, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
    }

    /**
     * The UI to add a theme.
     *
     * @return Tempcode The UI
     */
    public function add_theme()
    {
        $fields = $this->get_theme_fields();

        $post_url = build_url(array('page' => '_SELF', 'type' => '_add_theme'), '_SELF');
        $submit_name = do_lang_tempcode('ADD_THEME');

        if (addon_installed('themewizard')) {
            $theme_wizard_url = build_url(array('page' => 'admin_themewizard', 'type' => 'browse'), get_module_zone('admin_themewizard'));
            $text = do_lang_tempcode('DESCRIPTION_ADD_THEME_MANUAL', escape_html($theme_wizard_url->evaluate()));
        } else {
            $text = new Tempcode();
        }

        require_javascript('ajax');
        $script = find_script('snippet');
        $javascript = "
            var title=document.getElementById('title');
            title.onchange=function() {
                var codename=document.getElementById('theme');
                if (codename.value=='')
                {
                    codename.value=title.value.replace(/[^" . URL_CONTENT_REGEXP_JS . "]/g,'');
                }
            }
            var form=document.getElementById('main_form');
            form.old_submit=form.onsubmit;
            form.onsubmit=function() {
                document.getElementById('submit_button').disabled=true;
                var url='" . addslashes($script) . "?snippet=exists_theme&name='+window.encodeURIComponent(form.elements['theme'].value);
                if (!do_ajax_field_test(url))
                {
                    document.getElementById('submit_button').disabled=false;
                    return false;
                }
                document.getElementById('submit_button').disabled=false;
                if (typeof form.old_submit!='undefined' && form.old_submit) return form.old_submit();
                return true;
            };
        ";

        return do_template('FORM_SCREEN', array('_GUID' => '08b45be04f4035c7595458a719260bd9', 'HIDDEN' => '', 'JAVASCRIPT' => $javascript, 'TITLE' => $this->title, 'URL' => $post_url, 'FIELDS' => $fields, 'TEXT' => $text, 'SUBMIT_ICON' => 'menu___generic_admin__add_one', 'SUBMIT_NAME' => $submit_name, 'SUPPORT_AUTOSAVE' => true));
    }

    /**
     * The actualiser to add a theme.
     *
     * @return Tempcode The UI
     */
    public function _add_theme()
    {
        $theme = post_param_string('theme');
        require_code('type_sanitisation');
        if (!is_alphanumeric($theme)) {
            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');
                $theme = transliterate_string($theme);
            }

            $theme = preg_replace('#[^' . URL_CONTENT_REGEXP . ']#', '_', $theme);
            //warn_exit(do_lang_tempcode('BAD_CODENAME'));
        }
        actual_add_theme($theme);

        $this->save_theme_changes($theme);

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

    /**
     * The UI to edit/rename a theme.
     *
     * @return Tempcode The UI
     */
    public function edit_theme()
    {
        $theme = get_param_string('theme', false, true);

        if (($theme == 'default') || ($theme == 'admin')) {
            if ($GLOBALS['CURRENT_SHARE_USER'] !== null) {
                warn_exit(do_lang_tempcode('SHARED_INSTALL_PROHIBIT'));
            }
        }

        $ini_file = (($theme == 'default' || $theme == 'admin') ? get_file_base() : get_custom_file_base()) . '/themes/' . $theme . '/theme.ini';
        if (!file_exists($ini_file)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
        }
        $details = better_parse_ini_file($ini_file);
        if (!array_key_exists('title', $details)) {
            $details['title'] = '?';
        }
        if (!array_key_exists('description', $details)) {
            $details['description'] = '?';
        }
        if (!array_key_exists('author', $details)) {
            $details['author'] = '?';
        }
        if (!array_key_exists('mobile_pages', $details)) {
            $details['mobile_pages'] = '';
        }
        if (!array_key_exists('supports_wide', $details)) {
            $details['supports_wide'] = '1';
        }
        $fields = $this->get_theme_fields($theme, $details['title'], $details['description'], $details['author'], $details['mobile_pages'], intval($details['supports_wide']), false);
        if ($theme != 'default') {
            $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => '66a48b082356b9e9bfdb1a8107f5e567', 'TITLE' => do_lang_tempcode('ACTIONS'))));
            $fields->attach(form_input_tick(do_lang_tempcode('COPY_THEME'), do_lang_tempcode('DESCRIPTION_COPY_THEME', escape_html($theme)), 'copy', false));
            $fields->attach(form_input_tick(do_lang_tempcode('DELETE'), do_lang_tempcode('DESCRIPTION_DELETE'), 'delete', false));
        }

        $post_url = build_url(array('page' => '_SELF', 'type' => '_edit_theme', 'old_theme' => $theme), '_SELF');
        $submit_name = do_lang_tempcode('EDIT_THEME');

        $javascript = 'var themee=document.getElementById(\'theme\'), themet=document.getElementById(\'title\'), copy=document.getElementById(\'copy\'); if (copy) copy.onchange=function() { if (copy.checked && themee.value.indexOf(\'-copy\')==-1) { themee.value+=\'-copy\'; themet.value+=\' copy\'; } };';

        return do_template('FORM_SCREEN', array('_GUID' => '2734c55cd4d7cfa785d307d932ce8af1', 'JAVASCRIPT' => $javascript, 'HIDDEN' => '', 'TITLE' => $this->title, 'TEXT' => do_lang_tempcode('DESCRIPTION_EDIT_THEME'), 'URL' => $post_url, 'FIELDS' => $fields, 'SUBMIT_ICON' => 'menu___generic_admin__edit_this', 'SUBMIT_NAME' => $submit_name, 'SUPPORT_AUTOSAVE' => true));
    }

    /**
     * The actualiser to edit/rename a theme.
     *
     * @return Tempcode The UI
     */
    public function _edit_theme()
    {
        $theme = get_param_string('old_theme', false, true);

        if (($theme == 'default') || ($theme == 'admin')) {
            if ($GLOBALS['CURRENT_SHARE_USER'] !== null) {
                warn_exit(do_lang_tempcode('SHARED_INSTALL_PROHIBIT'));
            }
        }

        if (post_param_integer('delete', 0) == 1) {
            require_code('themes3');
            actual_delete_theme($theme);

            $to = '';
        } elseif (post_param_integer('copy', 0) == 1) {
            $to = post_param_string('theme', $theme); // Can't rename the default theme, so there's no such field for it
            if ($theme == $to) {
                warn_exit(do_lang_tempcode('ALREADY_EXISTS', escape_html($to)));
            }

            require_code('themes3');
            actual_copy_theme($theme, $to);

            $this->save_theme_changes($to);
        } else {
            $to = post_param_string('theme', $theme); // Can't rename the default theme, so there's no such field for it
            if ($theme != $to) {
                require_code('type_sanitisation');
                if (!is_alphanumeric($to)) {
                    $to = preg_replace('#[^' . URL_CONTENT_REGEXP . ']#', '_', $to);
                    //warn_exit(do_lang_tempcode('BAD_CODENAME'));
                }

                if (!file_exists(get_custom_file_base() . '/themes/' . $theme)) {
                    warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
                }

                require_code('themes3');
                actual_rename_theme($theme, $to);
            }

            $this->save_theme_changes($to);
        }

        return $this->do_next_manager($this->title, do_lang_tempcode('SUCCESS'), $to, '', 'theme', $to);
    }

    /**
     * The do-next manager for after download content management.
     *
     * @param  Tempcode $title The title (output of get_screen_title)
     * @param  Tempcode $description Some description to show, saying what happened
     * @param  ID_TEXT $theme The theme that was just handled
     * @param  ?LANGUAGE_NAME $lang The language we were working in (null: autodetect) (blank: autodetect)
     * @param  ID_TEXT $type Code to determine what kind of links to show
     * @param  ID_TEXT $file ID of file that an edit link should load (blank: N/A)
     * @return Tempcode The UI
     */
    public function do_next_manager($title, $description, $theme, $lang, $type, $file)
    {
        if (is_null($lang)) {
            $lang = '';
        }

        switch ($type) {
            case 'css':
                $add_one = null;
                $edit_this = array('_SELF', array('type' => 'edit_css', 'file' => str_replace('css_custom/', 'css/', $file), 'theme' => $theme), '_SELF');
                $edit_one = array('_SELF', array('type' => 'choose_css', 'theme' => $theme), '_SELF');
                $section_title = do_lang_tempcode('CSS');
                $content_type = do_lang('CSS_FILE');
                break;

            case 'templates':
                $add_one = null;
                $edit_this = array('_SELF', array('type' => '_edit_templates', 'f0file' => str_replace('_custom/', '/', $file), 'theme' => $theme), '_SELF');
                $edit_one = array('_SELF', array('type' => 'edit_templates', 'theme' => $theme), '_SELF');
                $section_title = do_lang_tempcode('TEMPLATES');
                $content_type = do_lang('TEMPLATE');
                break;

            case 'image':
                $add_one = array('_SELF', array('type' => 'add_image', 'theme' => $theme, 'lang' => $lang), '_SELF');
                $edit_this = array('_SELF', array('type' => 'edit_image', 'id' => $file, 'theme' => $theme, 'lang' => $lang), '_SELF');
                $edit_one = array('_SELF', array('type' => 'manage_images', 'theme' => $theme, 'lang' => $lang), '_SELF');
                $section_title = do_lang_tempcode('THEME_IMAGES');
                $content_type = do_lang('THEME_IMAGE');
                break;

            default:
                $add_one = null;
                $edit_this = null;
                $edit_one = null;
                $section_title = null;
                $content_type = null;
                break;
        }

        require_code('templates_donext');
        return do_next_manager($title, $description,
            null,
            null,
            /* TYPED-ORDERED LIST OF 'LINKS'  */
            $add_one, // Add one
            $edit_this, // Edit this
            $edit_one, // Edit one
            null, // View this
            null, // View archive
            null, // Add to category
            null, // Add one category
            null, // Edit one category
            null, // Edit this category
            null, // View this category
            /* SPECIALLY TYPED 'LINKS' */
            array(),
            array(),
            array(
                array('menu/_generic_admin/add_one', array('_SELF', array('type' => 'add_theme'), '_SELF'), do_lang_tempcode('ADD_THEME')), // Add one
                is_null($theme) ? null : array('menu/_generic_admin/edit_this', array('_SELF', array('type' => 'edit_theme', 'theme' => $theme), '_SELF'), do_lang_tempcode('EDIT_THEME')),
                is_null($theme) ? null : array('menu/adminzone/style/themes/css', array('_SELF', array('type' => 'choose_css', 'theme' => $theme), '_SELF'), do_lang('EDIT_CSS')),
                is_null($theme) ? null : array('menu/adminzone/style/themes/templates', array('_SELF', array('type' => 'edit_templates', 'theme' => $theme), '_SELF'), do_lang('EDIT_TEMPLATES')),
                is_null($theme) ? null : array('menu/adminzone/style/themes/theme_images', array('_SELF', array('type' => 'manage_images', 'theme' => $theme, 'lang' => $lang), '_SELF'), do_lang('EDIT_THEME_IMAGES')),
                array('menu/adminzone/style/themes/themes', array('_SELF', array('type' => 'browse'), '_SELF'), do_lang('MANAGE_THEMES'))
            ),
            do_lang('MANAGE_THEMES'),
            null,
            $section_title,
            null,
            $content_type
        );
    }

    /**
     * The UI to choose a theme to work with.
     *
     * @param  Tempcode $title The title to show when choosing a theme
     * @param  boolean $lang_too Whether to also choose a language
     * @return Tempcode The UI
     */
    public function choose_theme($title, $lang_too = false)
    {
        $themes = new Tempcode();
        $_themes = find_all_themes();
        require_code('form_templates');
        foreach ($_themes as $theme => $theme_title) {
            $themes->attach(form_input_list_entry($theme, ($theme == $GLOBALS['FORUM_DRIVER']->get_theme()), $theme_title));
        }
        $fields = form_input_list(do_lang_tempcode('THEME'), '', 'theme', $themes, null, true);

        if ($lang_too) { // We also need the language
            $langs = create_selection_list_langs();
            $fields->attach(form_input_list(do_lang_tempcode('LANGUAGE'), do_lang_tempcode('DESCRIPTION_LANGUAGE'), 'lang', $langs, null, true));
        }

        $post_url = get_self_url(false, false, null, false, true);

        return do_template('FORM_SCREEN', array('_GUID' => '01030dc8f338138ac36ff4f59c7892fc', 'GET' => true, 'SKIP_WEBSTANDARDS' => true, 'HIDDEN' => '', 'SUBMIT_ICON' => 'buttons__proceed', 'SUBMIT_NAME' => do_lang_tempcode('CHOOSE'), 'TITLE' => $title, 'FIELDS' => $fields, 'URL' => $post_url, 'TEXT' => ''));
    }

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

        $theme = get_param_string('theme', false, true);

        single_field__start();

        $css = new Tempcode();
        $files = array();
        $_dir = opendir(get_file_base() . '/themes/default/css');
        while (false !== ($file = readdir($_dir))) {
            if (strtolower(substr($file, -4, 4)) == '.css') {
                $files[] = $file;
            }
        }
        closedir($_dir);
        $_dir = opendir(get_file_base() . '/themes/default/css_custom');
        while (false !== ($file = readdir($_dir))) {
            if ((strtolower(substr($file, -4, 4)) == '.css') && (!in_array($file, $files))) {
                $files[] = $file;
            }
        }
        closedir($_dir);
        sort($files);
        foreach ($files as $file) {
            $css->attach(form_input_list_entry($file, $file == 'global.css'));
        }
        $fields = form_input_huge_list(do_lang_tempcode('FILE'), '', 'file', $css, null, true, true, 50);

        single_field__end();

        $post_url = build_url(array('page' => '_SELF', 'type' => 'edit_css', 'theme' => $theme), '_SELF', array(), false, true);

        $form = do_template('FORM_SINGLE_FIELD', array(
            '_GUID' => '26dfd6f0d77889f2438c2371e90738ag',
            'GET' => true,
            'NAME' => 'file',
            'LABEL' => do_lang_tempcode('FILE'),
            'SKIP_WEBSTANDARDS' => true,
            'HIDDEN' => '',
            'SUBMIT_ICON' => 'buttons__proceed',
            'SUBMIT_NAME' => do_lang_tempcode('EDIT'),
            'FIELD' => $fields,
            'URL' => $post_url,
            'TEXT' => do_lang_tempcode('CSS_CHOOSE_TO_EDIT'),
        ));

        return do_template('PAGINATION_SCREEN', array(
            '_GUID' => '16dfd6f0d77889f2438c2371e90738a3',
            'TITLE' => $this->title,
            'CONTENT' => $form,
        ));
    }

    /**
     * The UI to edit a CSS file.
     *
     * @return Tempcode The UI
     */
    public function edit_css()
    {
        $file = $this->file;

        if (get_option('editarea') == '1') {
            attach_to_screen_footer(make_string_tempcode('
                <script language="javascript" src="' . get_base_url() . '/data/editarea/edit_area_full.js"></script>
                <script>// <![CDATA[
                    editAreaLoader.init({
                        id : "css"
                        ,syntax: "css"
                        ,start_highlight: true
                        ,language: "' . (file_exists(get_file_base() . '/data/editarea/langs/' . strtolower(user_lang())) ? strtolower(user_lang()) : 'en') . '"
                        ,allow_resize: true
                        ,toolbar: "search, go_to_line, fullscreen, |, undo, redo, |, select_font,|, reset_highlight, word_wrap"
                    });
                //]]></script>')); // XHTMLXHTML
        }

        require_javascript('ajax');

        $theme = get_param_string('theme', '');
        if ($theme == '') {
            return $this->choose_theme($this->title);
        }

        $url = build_url(array('page' => '_SELF', 'type' => '_edit_css', 'file' => $file), '_SELF');

        $path = get_custom_file_base() . '/themes/' . filter_naughty($theme) . '/css_custom/' . $file;
        if (!file_exists($path)) {
            $path = get_custom_file_base() . '/themes/' . filter_naughty($theme) . '/css/' . $file;
        }
        if (!file_exists($path)) {
            $path = get_custom_file_base() . '/themes/default/css_custom/' . $file;
        }
        if (!file_exists($path)) {
            $path = get_file_base() . '/themes/default/css/' . $file;
        }
        if (file_exists($path)) {
            $css = unixify_line_format(cms_file_get_contents_safe($path));
        } else {
            $css = '';
        }

        if (addon_installed('actionlog')) {
            require_code('revisions_engine_files');
            $revision_engine = new RevisionEngineFiles();
            $revision_loaded = mixed();
            $revisions = $revision_engine->ui_revision_undoer('themes/' . $theme . '/css_custom', basename($file, '.css'), 'css', 'EDIT_CSS', $css, $revision_loaded);
        } else {
            $revisions = new Tempcode();
        }

        $old_contents = @file_get_contents(get_file_base() . '/themes/default/css/' . $file);
        if ($old_contents === false) {
            $old_contents = '';
        }

        require_code('form_templates');
        check_suhosin_request_size(strlen($css));

        set_short_title($file);

        $entries = new Tempcode();

        $advanced_mode = get_param_integer('advanced_mode', 0);
        if ($advanced_mode == 1) {
            require_javascript('theme_colours');

            global $CSS_MATCHES;
            $CSS_MATCHES = array();
            $css = preg_replace_callback('#\#[\da-f][\da-f][\da-f][\da-f][\da-f][\da-f]#i', 'css_preg', $css);

            foreach ($CSS_MATCHES as $id => $color) {
                $pos = strpos($css, '<color-' . $id . '>');
                $section_start = $pos;
                do {
                    $section_start = strrpos(substr($css, 0, $section_start), '{');
                    if ($section_start === false || $section_start < 5) {
                        break;
                    }
                } while (($css[$section_start - 2] == '*') || ($css[$section_start - 1] != ' '));
                $section_line_start = strrpos(substr($css, 0, $section_start), "\n");
                $line_start = strrpos(substr($css, 0, $pos), "\n");
                $context1 = substr($css, $section_line_start, $section_start - $section_line_start);
                $context2 = substr($css, $line_start, $pos - $line_start);
                $context = $context1 . '=>' . trim($context2);
                $entries->attach(do_template('THEME_COLOUR_CHOOSER', array('_GUID' => 'a42ef9daa06a95f5bb9d715aab1bd887', 'COLOR' => $color, 'NAME' => 'c' . strval($id), 'CONTEXT' => trim($context))));
            }
        }

        $switch_string = ($advanced_mode == 1) ? do_lang_tempcode('SWITCH_TO_SIMPLE_MODE') : do_lang_tempcode('SWITCH_TO_ADVANCED_MODE');
        $switch_icon = ($advanced_mode == 1) ? 'buttons__simple' : 'buttons__advanced';
        $switch_url = build_url(array('page' => '_SELF', 'type' => 'edit_css', 'file' => $file, 'theme' => $theme, 'advanced_mode' => 1 - $advanced_mode), '_SELF');

        require_code('form_templates');
        list($warning_details, $ping_url) = handle_conflict_resolution($file);

        return do_template('THEME_EDIT_CSS_SCREEN', array(
            '_GUID' => '3e34b2f44d7a73202f1bb475fd4ac593',
            'PING_URL' => $ping_url,
            'OLD_CONTENTS' => $old_contents,
            'WARNING_DETAILS' => $warning_details,
            'REVISIONS' => $revisions,
            'SWITCH_ICON' => $switch_icon,
            'SWITCH_STRING' => $switch_string,
            'SWITCH_URL' => $switch_url,
            'TITLE' => $this->title,
            'THEME' => $theme,
            'CSS' => $css,
            'URL' => $url,
            'FILE' => $file,
            'ENTRIES' => $entries,
        ));
    }

    /**
     * The actualiser to edit a CSS file.
     *
     * @return Tempcode The UI
     */
    public function _edit_css()
    {
        $file = filter_naughty(get_param_string('file', 'global.css'));

        $css = post_param_string('css');

        $theme = post_param_string('theme');

        // Apply colour selections (special colour picking mode)
        $i = 0;
        $color = post_param_string('c' . strval($i), '');
        while ($color != '') {
            $css = str_replace('<color-' . strval($i) . '>', '#' . $color, $css);
            $i++;

            $color = post_param_string('c' . strval($i), '');
        }

        // Convert from current path to save path
        $custom_path = get_custom_file_base() . '/themes/' . filter_naughty($theme) . '/css_custom/' . $file;

        // Store revision
        if (addon_installed('actionlog')) {
            require_code('revisions_engine_files');
            $revision_engine = new RevisionEngineFiles();
            $existing_path = $custom_path;
            if (!file_exists($existing_path)) {
                $existing_path = get_custom_file_base() . '/themes/default/css_custom/' . $file;
            }
            if (!file_exists($existing_path)) {
                $existing_path = get_custom_file_base() . '/themes/default/css/' . $file;
            }
            if (!file_exists($existing_path)) {
                $existing_path = get_file_base() . '/themes/default/css_custom/' . $file;
            }
            if (!file_exists($existing_path)) {
                $existing_path = get_file_base() . '/themes/default/css/' . $file;
            }
            if (file_exists($existing_path)) {
                $revision_engine->add_revision(dirname($custom_path), basename($custom_path, '.css'), 'css', file_get_contents($existing_path), filemtime($existing_path));
            }
        }

        require_code('files');

        // Save
        cms_file_put_contents_safe($custom_path, $css, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
        sync_file($custom_path);

        // Make base-hash-thingy
        $base_path = get_file_base() . '/themes/default/css/' . $file;
        if (is_file($base_path)) {
            $custom_path_edit_from = $custom_path . '.editfrom';
            $hash = file_get_contents($base_path);
            cms_file_put_contents_safe($custom_path_edit_from, $hash, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
        }

        require_code('caches3');
        erase_cached_templates(false, array(preg_replace('#\..*#', '', $file)));
        erase_cached_templates(false, null, TEMPLATE_DECACHE_BASE);

        log_it('EDIT_CSS', $theme, $file);

        if (get_param_integer('save_and_stay', 0) == 1) {
            return inform_screen($this->title, protect_from_escaping('
                <script>// <![CDATA[
                        window.fauxmodal_alert(\'' . addslashes(do_lang('SUCCESS')) . '\');
                //]]></script>
            '));
        }

        return $this->do_next_manager($this->title, do_lang_tempcode('SUCCESS'), $theme, '', 'css', $file);
    }

    /**
     * Get all the templates for a theme.
     *
     * @param  ID_TEXT $theme The theme to search for
     * @param  string $directory Subdirectory type to look in
     * @set    templates javascript xml text css
     * @param  string $suffix File type suffix of template file (e.g. .tpl)
     * @set    .tpl .js .xml .txt .css
     * @param  boolean $this_theme_only Just for this theme
     * @return array A map of the files (file=>path)
     */
    private function get_template_files_list($theme, $directory, $suffix, $this_theme_only = false)
    {
        $out = array();
        if (($theme == 'default') || (!$this_theme_only)) {
            $out = array_merge($out, $this->_get_template_files_list(get_file_base(), 'default/' . $directory, $suffix));
            $out = array_merge($out, $this->_get_template_files_list(get_custom_file_base(), 'default/' . $directory . '_custom', $suffix));
        }
        if ($theme != 'default') {
            $out = array_merge($out, $this->_get_template_files_list(get_custom_file_base(), $theme . '/' . $directory, $suffix));
            $out = array_merge($out, $this->_get_template_files_list(get_custom_file_base(), $theme . '/' . $directory . '_custom', $suffix));
        }
        ksort($out);

        return $out;
    }

    /**
     * Get all the template files / revisions for a template file, in a certain directory.
     *
     * @param  PATH $base_dir The path to search relative to
     * @param  PATH $subdir The subdirectory to search
     * @param  string $suffix File type suffix of template file (e.g. .tpl)
     * @set    .tpl .js .xml .txt .css
     * @return array A map of the revisions (file=>timestamp)
     */
    private function _get_template_files_list($base_dir, $subdir, $suffix)
    {
        $_dir = @opendir($base_dir . '/themes/' . $subdir);
        if ($_dir !== false) {
            // Find all the themes
            $files_list = array();
            while (false !== ($file = readdir($_dir))) {
                if (strtolower(substr($file, -strlen($suffix))) == $suffix) {
                    $files_list[$file] = $subdir . '/' . $file;
                }
            }
            closedir($_dir);
            return $files_list;
        }
        return array();
    }

    /**
     * The UI to choose a template to edit.
     *
     * @return Tempcode The UI
     */
    public function edit_templates()
    {
        require_javascript('ajax');
        require_code('form_templates');

        $theme = get_param_string('theme', '');
        if ($theme == '') {
            return $this->choose_theme($this->title);
        }

        $edit_forms = array();

        $types = array(
            array('templates', '.tpl', 'TEMPLATES_HTML'),
            array('javascript', '.js', 'TEMPLATES_JAVASCRIPT'),
            array('xml', '.xml', 'TEMPLATES_XML'),
            array('text', '.txt', 'TEMPLATES_TEXT'),
            array('css', '.css', 'TEMPLATES_CSS'),
        );
        foreach ($types as $i => $type) {
            list($directory, $suffix, $lang_string) = $type;

            $files_list = $this->get_template_files_list($theme, $directory, $suffix);
            $temp = form_input_list_entry('', false, do_lang_tempcode('NA_EM'));
            $files = '';
            $files_tmp = '';
            $stub = '';
            $new_stub = '';
            foreach ($files_list as $file) {
                $new_stub = dirname($file);
                if ($stub != $new_stub) {
                    if ($files_tmp != '') {
                        $temp = form_input_list_group($stub, make_string_tempcode($files_tmp));
                        $files .= $temp->evaluate(); // XHTMLXHTML
                    }
                    $files_tmp = '';
                    $stub = $new_stub;
                }
                $_file = substr($file, strrpos($file, '/') + 1);
                $temp = form_input_list_entry($directory . '/' . $_file, false, basename($file, $suffix));
                $files_tmp .= $temp->evaluate(); // XHTMLXHTML
            }
            if ($new_stub != '') {
                $temp = form_input_list_group($new_stub, make_string_tempcode($files_tmp));
                $files .= $temp->evaluate(); // XHTMLXHTML
            }

            $fields = new Tempcode();

            $set_name = 'template_' . strval($i);
            $required = true;
            $set_title = do_lang_tempcode($lang_string);
            $field_set = alternate_fields_set__start($set_name);

            if ($files != '') {
                $field_set->attach(form_input_multi_list(do_lang_tempcode('EXISTING'), '', 'f' . strval($i) . 'file', make_string_tempcode($files), null, 35));
            }

            $field_set->attach(form_input_line(do_lang_tempcode('SEARCH'), do_lang_tempcode('DESCRIPTION_TEMPLATES_SEARCH'), 'f' . strval($i) . 'search', '', false));

            $field_set->attach(form_input_codename(do_lang_tempcode('NEW'), do_lang_tempcode('NEW_TEMPLATE'), 'f' . strval($i) . 'file2', '', false));

            $fields->attach(alternate_fields_set__end($set_name, $set_title, '', $field_set, $required, null, true));

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

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

            $form = do_template('FORM', array(
                '_GUID' => 'b26747b4a29281baf83b31167c63582a',
                'GET' => true,
                'HIDDEN' => $hidden,
                'TEXT' => '',
                'URL' => $post_url,
                'FIELDS' => $fields,
                'SUBMIT_ICON' => 'buttons__proceed',
                'SUBMIT_NAME' => do_lang_tempcode('EDIT'),
                'SECONDARY_FORM' => ($i != 0),
            ));

            $edit_forms[] = array(
                '_TITLE' => do_lang_tempcode($lang_string),
                'FORM' => $form,
            );
        }

        list($warning_details, $ping_url) = handle_conflict_resolution(''); // Intentionally blank, because only one person should edit any of all templates at any time (because they depend on each other)

        return do_template('TEMPLATE_MANAGE_SCREEN', array(
            '_GUID' => '529cd009f85a84f60b7934b5e969c55b',
            'THEME' => $theme,
            'PING_URL' => $ping_url,
            'WARNING_DETAILS' => $warning_details,
            'TITLE' => $this->title,
            'EDIT_FORMS' => $edit_forms,
        ));
    }

    /**
     * The UI to edit a template.
     *
     * @return Tempcode The UI
     */
    public function _edit_templates()
    {
        $theme = get_param_string('theme');

        // Searching for something, which will provide links that loop back to the proper version of this page
        $search = '';
        foreach (array_keys($_GET) as $_i) {
            $matches = array();
            if (preg_match('#^f(\d+)search$#', $_i, $matches) != 0) {
                $i = $matches[1];
                $search = get_param_string('f' . $i . 'search', '', true);
            }
        }
        if ($search != '') {
            $directory = get_param_string('directory');

            $suffix = get_param_string('suffix');

            $files_list = $this->get_template_files_list($theme, $directory, $suffix);
            $results = new Tempcode();
            foreach ($files_list as $file) {
                $full_path = ((strpos($file, '/default/templates/') !== false) ? get_file_base() : get_custom_file_base()) . '/themes/' . $file;
                $contents = file_get_contents($full_path);
                if ((stripos($contents, $search) !== false) || (stripos($file, $search) !== false)) {
                    $_url = build_url(array('page' => '_SELF', 'type' => '_edit_templates', 'theme' => $theme, 'f0file' => $directory . '/' . basename($file)), '_SELF');
                    $results->attach(do_template('INDEX_SCREEN_ENTRY', array('_GUID' => 'ed744a45728f3d7c1082a3dda893f352', 'URL' => $_url, 'NAME' => $file)));
                }
            }
            return do_template('INDEX_SCREEN', array('_GUID' => '286a7ae3add44f935a9a2018dde3ccaf', 'TITLE' => $this->title, 'PRE' => do_lang_tempcode('RESULTS'), 'POST' => '', 'CONTENT' => $results));
        }

        require_javascript('editing');

        $post_url = build_url(array('page' => '_SELF', 'type' => '__edit_templates'), '_SELF');
        $preview_url = get_param_string('preview_url', '');

        // We support multi-list for getting here, so f0file can be an array, in which case we change that
        $get2 = array();
        for ($j = 0; $j < 5; $j++) {
            if (array_key_exists('f' . strval($j) . 'file', $_GET)) {
                if (is_array($_GET['f' . strval($j) . 'file'])) {
                    foreach ($_GET['f' . strval($j) . 'file'] as $f) {
                        $get2['f' . strval(count($get2)) . 'file'] = $f;
                    }
                    unset($_GET['f' . strval($j) . 'file']);
                }
            }
        }
        $_GET += $get2;

        $template_editors = new Tempcode();
        $templates = array();

        $files_seen = array();

        if (get_option('editarea') == '1') {
            attach_to_screen_footer(make_string_tempcode('
                <script language="javascript" src="' . get_base_url() . '/data/editarea/edit_area_full.js"></script>
            ')); // XHTMLXHTML
        }

        $count = 0;
        $first_id = '';
        foreach (array_keys($_GET) as $_i) {
            $matches = array();
            if (preg_match('#^f(\d+)file2?$#', $_i, $matches) != 0) {
                $i = $matches[1];
            } else {
                continue;
            }

            // The file we're editing
            $file = filter_naughty(get_param_string('f' . $i . 'file', ''));
            if ($file == '') {
                $file = filter_naughty(get_param_string('f' . $i . 'file2', ''));
            }
            if ($file == '') {
                continue;
            }
            if ($file[0] == '!') {
                warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN_UPLOAD'));
            }
            $directory = dirname($file);
            if ($directory == '.' || $directory == '') {
                $directory = get_param_string('directory', 'templates');
                $file = $directory . '/' . $file;
            }
            $ext = get_file_extension($file);
            if ($ext == '') {
                $ext = ltrim(get_param_string('suffix', '.tpl'), '.');
                $file .= '.' . $ext;
            }

            // De-dupe
            if (in_array($file, $files_seen)) {
                continue;
            }
            $files_seen[] = $file;

            // Screen preview feature
            if (get_param_string('preview_url', '') == '') {
                require_code('lorem');
                $all_previews = find_all_previews__by_template();
                if (array_key_exists($file, $all_previews)) {
                    $_preview_url = build_url(array('page' => '_SELF', 'type' => 'screen_preview', 'id' => $file, 'hook' => $all_previews[$file][0], 'function' => $all_previews[$file][1], 'arg' => '', 'keep_theme' => $theme, 'keep_wide_high' => 1), '_SELF');
                    $preview_url = $_preview_url->evaluate();
                }
            }

            // Get file path
            $_default_load_path = find_template_place(basename($file, '.' . $ext), null, $theme, '.' . $ext, dirname($file));
            if ($_default_load_path === null) {
                attach_message(do_lang_tempcode('_MISSING_RESOURCE', escape_html($file)), 'warn');
                continue;
            }

            // Syntax highlighting
            $syntax = 'html';
            if (substr($file, -3) == '.js') {
                $syntax = 'js';
            } elseif (substr($file, -4) == '.xml') {
                $syntax = 'xml';
            } elseif (substr($file, -3) == '.txt') {
                $syntax = 'txt';
            } elseif (substr($file, -4) == '.css') {
                $syntax = 'css';
            }
            if (get_option('editarea') == '1') {
                attach_to_screen_footer('
                    <script>// <![CDATA[
                    editAreaLoader.init({
                        id : "f' . $i . '_new"
                        ' . (($syntax == 'txt') ? '' : (',syntax: "' . $syntax . '"')) . '
                        ,start_highlight: true
                        ,language: "' . (file_exists(get_file_base() . '/data/editarea/langs/' . strtolower(user_lang())) ? strtolower(user_lang()) : 'en') . '"
                        ,allow_resize: true
                        ,toolbar: "search, go_to_line, fullscreen, |, undo, redo, |, select_font,|, reset_highlight, word_wrap"
                    });
                    //]]></script>
                    '); // XHTMLXHTML
            }

            $codename = basename($file);

            // The file we're LOADING from for edit
            $contents = '';
            $revisions = new Tempcode();
            if (!is_null($_default_load_path[0])) {
                $default_load_path = 'themes/' . $_default_load_path[0] . $_default_load_path[1] . '/' . $codename;
                $full_path = get_custom_file_base() . '/' . $default_load_path;
                if (!is_file($full_path)) {
                    $full_path = get_file_base() . '/' . $default_load_path;
                }
                if (file_exists($full_path)) {
                    $contents = file_get_contents($full_path);
                }

                // Revisions
                if (addon_installed('actionlog')) {
                    require_code('revisions_engine_files');
                    $revision_engine = new RevisionEngineFiles();
                    $revision_loaded = mixed();
                    $revisions = $revision_engine->ui_revision_undoer(dirname($default_load_path), basename($file, '.' . $ext), $ext, 'EDIT_TEMPLATES', $contents, $revision_loaded);
                }
            }

            // Old contents (for very easy compare to base file in default theme)
            $_path = get_file_base() . '/themes/default/' . $file;
            if (file_exists($_path)) {
                $old_contents = cms_file_get_contents_safe($_path);
            } else {
                $old_contents = '';
            }

            // Lots of UI stuff...

            $matches = array();
            $cnt = preg_match_all('#\{([\w]\w*)[\*;%\#]?\}#', $old_contents, $matches);
            $parameters = new Tempcode();
            $p_done = array();
            for ($j = 0; $j < $cnt; $j++) {
                if (array_key_exists($matches[1][$j], $p_done)) {
                    continue;
                }
                $p_done[$matches[1][$j]] = 1;
                $parameters->attach(form_input_list_entry($matches[1][$j] . '__0', false, $matches[1][$j]));
            }
            $parameters = do_template('TEMPLATE_EDIT_SCREEN_DROPDOWN', array('_GUID' => '50f31c49c99b864c1719fb51ed426274', 'ID' => $i, 'PARAMETERS' => $parameters, 'NAME' => 'ppparameter', 'LANG' => do_lang_tempcode('INSERT_PARAMETER')));

            $_directives = array(
                array('BOX', '1'),
                array('WHILE', '1'),
                array('IF_NON_EMPTY', '1'),
                array('IF_EMPTY', '1'),
                array('IF', '1'),
                array('SET', '1'),
                array('LOOP', '1'), // To simplify things, we won't throw all options at the user
            );
            $directives = $this->generate_from($_directives, 'DIRECTIVE', $i);

            $_programmatic_symbols = array(
                array('RAND', '0'),
                array('SET_RAND', '0+'),
                array('CYCLE', '1+'),
                array('INIT', '1'),
                array('SET', '2'),
                array('GET', '1'),
                array('INC', '1'),
                array('DEC', '1'),
            );
            $programmatic_symbols = $this->generate_from($_programmatic_symbols, 'PROGRAMMATIC_SYMBOL', $i);

            $_abstraction_symbols = array(
                array('IMG', '1'),
                array('PAGE_LINK', '1'),
                array('MAILTO', '0'),
                array('BLOCK', '2'),
                array('THUMBNAIL', '2'),
                array('LOAD_PAGE', '1'),
                array('LOAD_PANEL', '1'),
            );
            $abstraction_symbols = $this->generate_from($_abstraction_symbols, 'ABSTRACTION_SYMBOL', $i);

            $_symbols = array(
                array('THEME', '0'),
                array('LANG', '0'),
                array('BASE_URL', '0'),
                array('MOBILE', '0'),
                array('SITE_NAME', '0'),
                array('STAFF_ADDRESS', '0'),
                array('MEMBER', '0'),
                array('DATE_AND_TIME', '0'),
                array('DATE', '0'),
                array('TIME', '0'),
                array('USERNAME', '0-1'),
                array('IS_STAFF', '0'),
                array('MATCH_KEY_MATCH', '1'),
            );
            $symbols = $this->generate_from($_symbols, 'SYMBOL', $i);

            $_arithmetical_symbols = array(
                array('MAX', '2'),
                array('MIN', '2'),
                array('REM', '2'),
                array('DIV', '2'),
                array('SUBTRACT', '2'),
                array('ADD', '2'),
                array('ROUND', '2'),
                array('MULT', '2'),
            );
            $arithmetical_symbols = $this->generate_from($_arithmetical_symbols, 'ARITHMETICAL_SYMBOL', $i);

            $_formatting_symbols = array(
                array('WCASE', '1'),
                array('LCASE', '1'),
                array('UCASE', '1'),
                array('REPLACE', '3'),
                array('AT', '2'),
                array('SUBSTR', '3'),
                array('LENGTH', '1'),
                array('WORDWRAP', '2'),
                array('TRUNCATE_LEFT', '2'),
                array('TRUNCATE_SPREAD', '2'),
            );
            $formatting_symbols = $this->generate_from($_formatting_symbols, 'FORMATTING_SYMBOL', $i);

            $_logical_symbols = array(
                array('NOT', '1'),
                array('OR', '2'),
                array('AND', '2'),
                array('EQ', '2'),
                array('NEQ', '2'),
                array('LT', '2'),
                array('GT', '2'),
            );
            $logical_symbols = $this->generate_from($_logical_symbols, 'LOGICAL_SYMBOL', $i);

            $guid = get_param_string('f' . $i . 'guid', '?');
            if ($guid == '?') {
                $guid = '';
            }

            $guids = array();
            $file_bits = explode('/', $file);
            $clean_file = str_replace('.' . get_file_extension($file), '', $file_bits[count($file_bits) - 1]);
            $_guids = @unserialize(@file_get_contents(get_file_base() . '/data/guids.bin'));
            if (($_guids !== false) && (array_key_exists($clean_file, $_guids))) {
                foreach ($_guids[$clean_file] as $_guid) {
                    $guids[] = array('FILENAME' => $_guid[0], 'LINE' => integer_format($_guid[1]), 'THIS_GUID' => $_guid[2]);
                }
            }

            $templates[] = array('I' => $i, 'FILE' => $file);

            $display = ($count == 0) ? 'block' : 'none';
            if ($count == 0) {
                $first_id = $i;
            }

            $file_save_target = preg_replace('#^default/#', $theme . '/', $file);

            $template_editors->attach(do_template('TEMPLATE_EDIT_SCREEN_EDITOR', array(
                '_GUID' => '9d3b75215c34c2b4b366118605b4cd59',
                'PREVIEW_URL' => $preview_url,
                'CODENAME' => str_replace('.' . get_file_extension($file), '', $codename),
                'I' => $i,
                'DISPLAY' => $display,
                'GUIDS' => $guids,
                'GUID' => $guid,
                'ARITHMETICAL_SYMBOLS' => $arithmetical_symbols,
                'FORMATTING_SYMBOLS' => $formatting_symbols,
                'LOGICAL_SYMBOLS' => $logical_symbols,
                'ABSTRACTION_SYMBOLS' => $abstraction_symbols,
                'PARAMETERS' => $parameters,
                'DIRECTIVES' => $directives,
                'PROGRAMMATIC_SYMBOLS' => $programmatic_symbols,
                'SYMBOLS' => $symbols,
                'FILE' => $file,
                'FILE_SAVE_TARGET' => $file_save_target,
                'OLD_CONTENTS' => $old_contents,
                'CONTENTS' => $contents,
                'REVISIONS' => $revisions
            )));

            $count++;
        }

        if ($count == 1) {
            $file_save_target = preg_replace('#^default/#', $theme . '/', $files_seen[0]);
            $this->title = get_screen_title('_EDIT_TEMPLATE', true, array(escape_html($file_save_target)));

            set_short_title($files_seen[0]);
        }

        if ($template_editors->is_empty_shell()) {
            warn_exit(do_lang_tempcode('NO_TEMPLATES_SELECTED'));
        }

        return do_template('TEMPLATE_EDIT_SCREEN', array(
            '_GUID' => 'cbcd6d18c2456f94f72624e1a39a36a5',
            'TITLE' => $this->title,
            'MULTIPLE' => $count > 1,
            'FIRST_ID' => $first_id,
            'THEME' => $theme,
            'TEMPLATES' => $templates,
            'URL' => $post_url,
            'TEMPLATE_EDITORS' => $template_editors,
            'COUNT' => strval($count),
        ));
    }

    /**
     * Helper function to get Tempcode for insertion of symbols.
     *
     * @param  array $array A list of pairs (symbol name,arity)
     * @param  string $stub The "stub" that determines what language strings to lookup for the given symbols, and generally, the collective naming strategy
     * @param  string $id The ID of the actual template editor we are working with
     * @return Tempcode The Tempcode
     */
    public function generate_from($array, $stub, $id)
    {
        $out = new Tempcode();
        foreach ($array as $x) {
            list($op, $arity) = $x;
            $lang = do_lang_tempcode($stub . '__' . $op);
            $out->attach(form_input_list_entry($op . '__' . $arity, false, $lang));
        }
        return do_template('TEMPLATE_EDIT_SCREEN_DROPDOWN', array('_GUID' => 'c6d24f278ec874a4b6abff8c359f80ba', 'ID' => $id, 'PARAMETERS' => $out, 'NAME' => 'pp' . strtolower($stub), 'LANG' => do_lang_tempcode('INSERT_' . $stub)));
    }

    /**
     * The actualiser to edit a template. Always saves to the most overridden version.
     *
     * @return Tempcode The UI
     */
    public function __edit_templates()
    {
        $theme = $this->theme;

        require_code('caches3');

        $file = mixed();

        foreach (array_keys($_REQUEST) as $_i) {
            $matches = array();
            if (preg_match('#^f(\d+)file$#', $_i, $matches) != 0) {
                $i = $matches[1];
            } else {
                continue;
            }

            $_file = substr(str_replace('/default/', '/' . $theme . '/', '/' . filter_naughty(post_param_string('f' . $i . 'file', ''))), 1);
            if ($_file == '') {
                continue;
            }

            // Convert from current path to save path
            $directory = dirname($_file);
            if ($directory == '.' || $directory == '') {
                $file = 'templates_custom/' . $_file;
            } else {
                $file = str_replace($directory . '/', $directory . '_custom/', $_file);
            }
            $full_path = get_custom_file_base() . '/themes/' . filter_naughty($theme) . '/' . $file;

            // Store revision
            if (addon_installed('actionlog')) {
                if (file_exists($full_path)) {
                    require_code('revisions_engine_files');
                    $revision_engine = new RevisionEngineFiles();
                    $existing_path = $full_path;
                    if (!file_exists($existing_path)) {
                        $existing_path = get_custom_file_base() . '/themes/default/' . $_file;
                    }
                    if (!file_exists($existing_path)) {
                        $existing_path = get_file_base() . '/themes/default/' . $_file;
                    }
                    $revision_engine->add_revision(dirname($full_path), basename($_file, '.' . get_file_extension($_file)), get_file_extension($_file), file_get_contents($existing_path), filemtime($existing_path));
                }
            }

            // Save
            $new = post_param_string('f' . $i . '_new', false, true);
            $full_path_orig = preg_replace('#/themes/[^/]*/(.*)(\_custom)?/#U', '/themes/default/${1}/', $full_path);
            if ((file_exists($full_path_orig)) && ($new == file_get_contents($full_path_orig))) {
                if (file_exists($full_path)) {
                    unlink($full_path);
                    sync_file($full_path);
                }
                if (file_exists($full_path . '.editfrom')) {
                    unlink($full_path . '.editfrom');
                    sync_file($full_path . '.editfrom');
                }
                $_file = preg_replace('#[^/]*/(.*)(\_custom)?/#U', 'default/${1}/', $_file);

                $file = $_file;
            } else {
                require_code('files');

                cms_file_put_contents_safe($full_path, $new, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);

                if (file_exists(get_file_base() . '/themes/default/' . post_param_string('f' . $i . 'file'))) {
                    // Make base-hash-thingy
                    $full_path_edit_from = $full_path . '.editfrom';
                    $hash = file_get_contents(get_file_base() . '/themes/default/' . post_param_string('f' . $i . 'file'));
                    cms_file_put_contents_safe($full_path_edit_from, $hash, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);
                }
            }
            log_it('EDIT_TEMPLATES', $file, $theme);

            // Erase cache
            erase_cached_templates(false, array(preg_replace('#\..*#', '', basename($file))));
        }

        // Erase cache
        erase_cached_templates(false, null, TEMPLATE_DECACHE_BASE);
        erase_block_cache(false, $theme);

        if (get_param_integer('save_and_stay', 0) == 1) {
            return inform_screen($this->title, protect_from_escaping('
                <script>// <![CDATA[
                        window.fauxmodal_alert(\'' . addslashes(do_lang('SUCCESS')) . '\');
                //]]></script>
            '));
        }

        if (is_null($file)) {
            warn_exit(do_lang_tempcode('NOTHING_SELECTED'));
        }

        return $this->do_next_manager($this->title, do_lang_tempcode('SUCCESS'), $theme, '', 'templates', $file);
    }

    /**
     * Get Tempcode for a theme image adding/editing form.
     *
     * @param  ID_TEXT $theme The theme the theme image is in
     * @param  LANGUAGE_NAME $lang The language the theme image is for
     * @param  SHORT_TEXT $id The theme image ID
     * @param  URLPATH $path The URL to the theme image
     * @return array A pair: the Tempcode for the visible fields, and the Tempcode for the hidden fields
     */
    public function get_image_form_fields($theme, $lang, $id = '', $path = '')
    {
        $fields = new Tempcode();
        $hidden = new Tempcode();
        require_code('form_templates');
        $hidden->attach(form_input_hidden('theme', $theme));
        $hidden->attach(form_input_hidden('lang', $lang));
        $fields->attach(form_input_line(do_lang_tempcode('CODENAME'), do_lang_tempcode('DESCRIPTION_THEME_IMAGE_NAME'), 'id', $id, true, null, null, 'text', 'some/path/name'));

        /* Actually we don't want to allow selection from existing -- too weird, creating these cross-links
        $list = combo_get_image_paths($path, get_base_url() . '/themes/' . rawurlencode($theme) . '/images/', get_file_base() . '/themes/' . filter_naughty($theme) . '/images/');
        $list->attach(combo_get_image_paths($path, get_base_url() . '/themes/' . rawurlencode($theme) . '/images_custom/', get_file_base() . '/themes/' . filter_naughty($theme) . '/images_custom/'));
        if ($theme != 'default') {
            $list->attach(combo_get_image_paths($path, get_base_url() . '/themes/default/images/', get_file_base() . '/themes/default/images/'));
            $list->attach(combo_get_image_paths($path, get_base_url() . '/themes/default/images_custom/', get_file_base() . '/themes/default/images_custom/'));
        }
        */
        handle_max_file_size($hidden, 'image');

        $set_name = 'image';
        $required = true;
        $set_title = do_lang_tempcode('IMAGE');
        $field_set = alternate_fields_set__start($set_name);

        $field_set->attach(form_input_upload(do_lang_tempcode('UPLOAD'), '', 'file', false, null, null, true, str_replace(' ', '', get_option('valid_images'))));
        //$fields->attach(form_input_radio(do_lang_tempcode('CHOOSE'), '', $list));

        $field_set->attach(form_input_url(do_lang_tempcode('URL'), '', 'path', $path, false));

        $fields->attach(alternate_fields_set__end($set_name, $set_title, '', $field_set, $required));

        return array($fields, $hidden);
    }

    /**
     * The UI to add a theme image.
     *
     * @return Tempcode The UI
     */
    public function add_image()
    {
        $theme = get_param_string('theme');
        $lang = get_param_string('lang', user_lang());
        if ($lang == '') {
            $lang = user_lang();
        }
        list($fields, $hidden) = $this->get_image_form_fields($theme, $lang);
        $fields->attach(form_input_tick(do_lang_tempcode('USE_ALL_THEMES'), do_lang_tempcode('DESCRIPTION_USE_ALL_THEMES'), 'use_all_themes', false));
        $fields->attach(form_input_tick(do_lang_tempcode('USE_ALL_LANGS'), do_lang_tempcode('DESCRIPTION_USE_ALL_LANGS'), 'use_all_langs', true));

        $post_url = build_url(array('page' => '_SELF', 'type' => '_add_image', 'uploading' => 1), '_SELF');
        $submit_name = do_lang_tempcode('ADD_THEME_IMAGE');

        $text = new Tempcode();

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

        require_code('images');
        $max = floatval(get_max_image_size()) / floatval(1024 * 1024);
        if ($max < 3.0) {
            require_code('files2');
            $config_url = get_upload_limit_config_url();
            $text->attach(paragraph(do_lang_tempcode(is_null($config_url) ? 'MAXIMUM_UPLOAD' : 'MAXIMUM_UPLOAD_STAFF', escape_html(($max > 10.0) ? integer_format(intval($max)) : float_format($max)), escape_html(is_null($config_url) ? '' : $config_url))));
        }

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

    /**
     * The actualiser to add a theme image.
     *
     * @return Tempcode The UI
     */
    public function _add_image()
    {
        require_code('uploads');

        $theme = post_param_string('theme');
        $lang = post_param_string('lang');
        $id = post_param_string('id');
        $use_all_themes = post_param_integer('use_all_themes', 0);
        $use_all_langs = post_param_integer('use_all_langs', 0);

        $target_dir = 'themes/' . (($use_all_themes == 1) ? 'default' : $theme) . '/images_custom';
        if ((strpos($id, '/') !== false) && (str_replace(array('on', 'true', 'yes'), array('1', '1', '1'), strtolower(ini_get('safe_mode'))) != '1')) {
            $target_dir .= '/' . dirname($id);
        }
        $path = get_url('path', 'file', $target_dir);
        if ($path[0] == '') {
            return warn_screen($this->title, do_lang_tempcode('IMPROPERLY_FILLED_IN_UPLOAD'));
        }

        $theme_list = array_keys(find_all_themes());
        $lang_list = find_all_langs(true);

        if (($use_all_themes == 1) && ($use_all_langs == 1)) {
            $GLOBALS['SITE_DB']->query_delete('theme_images', array('id' => $id));
            foreach ($theme_list as $theme) {
                foreach (array_keys($lang_list) as $_lang) {
                    actual_add_theme_image($theme, $_lang, $id, $path[0], true);
                }
            }
        } elseif ($use_all_themes == 1) {
            $GLOBALS['SITE_DB']->query_delete('theme_images', array('id' => $id, 'lang' => $lang));
            foreach ($theme_list as $theme) {
                actual_add_theme_image($theme, $lang, $id, $path[0], true);
            }
        } elseif ($use_all_langs == 1) {
            $GLOBALS['SITE_DB']->query_delete('theme_images', array('id' => $id, 'theme' => $theme));
            foreach (array_keys($lang_list) as $_lang) {
                actual_add_theme_image($theme, $_lang, $id, $path[0], true);
            }
        } else {
            actual_add_theme_image($theme, $lang, $id, $path[0]);
        }

        require_code('caches3');
        Self_learning_cache::erase_smart_cache();
        erase_cached_templates(false, null, TEMPLATE_DECACHE_WITH_THEME_IMAGE);

        return $this->do_next_manager($this->title, do_lang_tempcode('SUCCESS'), $theme, $lang, 'image', $id);
    }

    /**
     * The UI to select a theme image to edit.
     *
     * @return Tempcode The UI
     */
    public function manage_images()
    {
        if (php_function_allowed('set_time_limit')) {
            @set_time_limit(300);
        }
        send_http_output_ping();

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

        $GLOBALS['NO_QUERY_LIMIT'] = true;

        $theme = get_param_string('theme', '');
        if ($theme == '') {
            return $this->choose_theme($this->title, true);
        }

        require_code('themes3');
        regen_theme_images($theme, array($lang => 1));
        if ($theme != 'default') {
            regen_theme_images('default', array($lang => 1), $theme);
        }

        require_code('form_templates');
        require_code('themes2');

        $skip = array(
            'icons', // Too many of these to show
        );
        $ids = get_all_image_ids_type('', true, $GLOBALS['SITE_DB'], $theme, false, true, $skip); // The final 'true' stops new theme images being detected, as we know regen_theme_images did that (and more conservatively - it won't scan images_custom dirs for NEW codes which an unbridled get_all_image_ids_type call would)

        single_field__start();
        $fields = form_input_theme_image(do_lang_tempcode('CODENAME'), '', 'id', $ids, null, null, null, false, null, $theme, $lang, true, true);
        single_field__end();

        $hidden = form_input_hidden('theme', $theme);

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

        $edit_form = do_template('FORM_SINGLE_FIELD', array(
            '_GUID' => '48b3218750fcea21e0bf3be31ae58296',
            'HIDDEN' => $hidden,
            'TEXT' => do_lang_tempcode('CHOOSE_EDIT_LIST'),
            'GET' => true,
            'URL' => $post_url,
            'FIELD' => $fields,
            'SUBMIT_ICON' => 'buttons__proceed',
            'SUBMIT_NAME' => has_js() ? new Tempcode() : do_lang_tempcode('EDIT'), // We don't want a button if JS is on because clicking on images takes you through
        ));

        $add_url = build_url(array('page' => '_SELF', 'type' => 'add_image', 'theme' => $theme, 'lang' => $lang), '_SELF');

        return do_template('THEME_IMAGE_MANAGE_SCREEN', array('_GUID' => '4e760b0aa59b1bbb6fcf289c0b93ec46', 'ADD_URL' => $add_url, 'TITLE' => $this->title, 'FORM' => $edit_form));
    }

    /**
     * The UI to edit a theme image.
     *
     * @return Tempcode The UI
     */
    public function edit_image()
    {
        $lang = choose_language($this->title, true, true);
        if (is_object($lang)) {
            return $lang;
        }

        $id = $this->id;
        $theme = $this->theme;

        $where_map = array('theme' => $theme, 'id' => $id);
        if ($lang != '') {
            $where_map['lang'] = $lang;
        }
        $path = $GLOBALS['SITE_DB']->query_select_value_if_there('theme_images', 'path', $where_map);
        if (is_null($path)) {
            $path = $GLOBALS['SITE_DB']->query_select_value_if_there('theme_images', 'path', array('theme' => $theme, 'lang' => get_site_default_lang(), 'id' => $id));
        }
        if (is_null($path)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
        }

        set_short_title($id);

        $unmodified = (strpos($path, 'themes/default/images/') !== false);

        disable_php_memory_limit();
        $full_path = ($unmodified ? get_file_base() : get_custom_file_base()) . '/' . rawurldecode($path);
        $width = do_lang_tempcode('UNKNOWN_EM');
        $height = do_lang_tempcode('UNKNOWN_EM');
        $image_size = cms_getimagesize($full_path);
        if ($image_size !== false) {
            $width = make_string_tempcode(strval($image_size[0]));
            $height = make_string_tempcode(strval($image_size[1]));
        }

        $url = ($unmodified ? get_base_url() : get_custom_base_url()) . '/' . $path;
        $text = do_template('THEME_IMAGE_PREVIEW', array('_GUID' => 'c71817851526064e738d5076dcd1bce1', 'WIDTH' => $width, 'HEIGHT' => $height, 'URL' => $url, 'UNMODIFIED' => $unmodified));

        list($fields, $hidden) = $this->get_image_form_fields($theme, $lang, $id, $path);
        $hidden->attach(form_input_hidden('old_id', $id));
        if (strpos($path, 'images_custom') !== false) {
            $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => '9468297854009243da7b47c9bb3992bb', 'TITLE' => do_lang_tempcode('ACTIONS'))));
            $fields->attach(form_input_tick(do_lang_tempcode('DELETE'), do_lang_tempcode('DESCRIPTION_DELETE_THEME_IMAGE'), 'delete', false));
        }

        $post_url = build_url(array('page' => '_SELF', 'type' => '_edit_image', 'uploading' => 1), '_SELF', null, false, true);
        $submit_name = do_lang_tempcode('SAVE');

        require_code('images');
        $max = floatval(get_max_image_size()) / floatval(1024 * 1024);
        if ($max < 3.0) {
            require_code('files2');
            $config_url = get_upload_limit_config_url();
            $text->attach(paragraph(do_lang_tempcode(is_null($config_url) ? 'MAXIMUM_UPLOAD' : 'MAXIMUM_UPLOAD_STAFF', escape_html(($max > 10.0) ? integer_format(intval($max)) : float_format($max)), escape_html(is_null($config_url) ? '' : $config_url))));
        }

        return do_template('FORM_SCREEN', array('_GUID' => 'b0e178ad1f840a07c4967f3c266c750b', 'HIDDEN' => $hidden, 'TITLE' => $this->title, 'URL' => $post_url, 'FIELDS' => $fields, 'TEXT' => $text, 'SUBMIT_ICON' => 'menu___generic_admin__edit_this', 'SUBMIT_NAME' => $submit_name));
    }

    /**
     * The actualiser to edit a theme image.
     *
     * @return Tempcode The UI
     */
    public function _edit_image()
    {
        require_code('uploads');

        $theme = post_param_string('theme');
        $lang = post_param_string('lang');
        $id = post_param_string('id');
        $old_id = post_param_string('old_id');

        if (post_param_integer('delete', 0) == 1) {
            require_code('themes3');
            actual_delete_theme_image($old_id, $theme, $lang);
        } else {
            // Remove old file first so we can re-use the filepath
            is_plupload(true);
            if (((array_key_exists('file', $_FILES)) && ((is_plupload()) || (is_uploaded_file($_FILES['file']['tmp_name'])))) || (post_param_string('path', '') != '')) {
                $old_url = find_theme_image($old_id, true, true, $theme, ($lang == '') ? null : $lang);
                if ($old_url != '' && ((array_key_exists('file', $_FILES)) && ((is_plupload()) || (is_uploaded_file($_FILES['file']['tmp_name']))) || $old_url != post_param_string('path', ''))) {
                    if (($theme == 'default' || $theme == 'admin') || (strpos($old_url, 'themes/default/') === false)) {
                        $where_map = array('theme' => $theme, 'id' => $old_id);
                        if (($lang != '') && (!is_null($lang))) {
                            $where_map['lang'] = $lang;
                        }
                        $GLOBALS['SITE_DB']->query_delete('theme_images', $where_map);

                        require_code('themes3');
                        cleanup_after_theme_image_file_removal($old_url);
                    }
                }
            }

            $target_dir = 'themes/' . $theme . '/images_custom';
            if (str_replace(array('on', 'true', 'yes'), array('1', '1', '1'), strtolower(ini_get('safe_mode'))) != '1') {
                if (strpos($id, '/') !== false) {
                    $target_dir .= '/' . dirname($id);
                }
            }
            $path = get_url('path', 'file', $target_dir);

            if ((url_is_local($path[0])) && (!file_exists(((substr($path[0], 0, 15) == 'themes/default/') ? get_file_base() : get_custom_file_base()) . '/' . rawurldecode($path[0])))) {
                warn_screen($this->title, do_lang_tempcode('IMPROPERLY_FILLED_IN_UPLOAD'));
            }

            if ($path[0] == '') {
                return warn_screen($this->title, do_lang_tempcode('IMPROPERLY_FILLED_IN_UPLOAD'));
            }
            actual_edit_theme_image($old_id, $theme, $lang, $id, $path[0]);
        }

        require_code('caches3');
        Self_learning_cache::erase_smart_cache();
        erase_cached_templates(false, null, TEMPLATE_DECACHE_WITH_THEME_IMAGE);
        persistent_cache_delete('IMAGE_DIMS');

        return $this->do_next_manager($this->title, do_lang_tempcode('SUCCESS'), $theme, $lang, 'image', $id);
    }

    /**
     * Shows the list of templates
     *
     * @return Tempcode The UI
     */
    public function list_screen_previews()
    {
        if (php_function_allowed('set_time_limit')) {
            @set_time_limit(120);
        }
        send_http_output_ping();

        require_code('lorem');

        // Find all templates
        $templates = array();
        $dh = opendir(get_file_base() . '/themes/default/templates');
        while (($f = readdir($dh)) !== false) {
            if (strtolower(substr($f, -4)) == '.tpl') {
                $templates[] = 'templates/' . $f;
            }
        }
        sort($templates);

        // Find all previews (map of templates to previews)
        $all_previews = find_all_previews__by_template();
        // And by screen
        $all_previews__by_screen = find_all_previews__by_screen();

        // Find other things we may want to preview
        $comcode_files = find_comcodes();
        $html_files = find_html();

        // Loop over to display it all
        $displayed_already = array();
        $lis = new Tempcode();
        $lis_admin = new Tempcode();
        foreach ($templates as $t) {
            // If we have a preview for it
            if (array_key_exists($t, $all_previews)) {
                if (!array_key_exists($all_previews[$t][1], $displayed_already)) {
                    $func = $all_previews[$t][1];

                    $preview_url = build_url(array('page' => '_SELF', 'type' => 'screen_preview', 'id' => $t, 'hook' => $all_previews[$t][0], 'function' => $func), '_SELF');

                    $template_used = "(" . implode(', ', $all_previews__by_screen[$func]) . ")";

                    $tpl_x = do_template('TEMPLATE_LIST', array('_GUID' => '1f27f619db553dfcb8d427e70a736226', 'URL' => $preview_url, 'COLOR' => 'green', 'TEMPLATE' => preg_replace('#^tpl_preview\_\_#', '', $func), 'LIST' => $template_used));
                    if (preg_match('#^tpl_preview\_\_administrative\_\_#', $func) != 0) {
                        $lis_admin->attach($tpl_x);
                    } else {
                        $lis->attach($tpl_x);
                    }

                    $displayed_already[$func] = true;
                }
            } else {
                // No preview for these
                $tpl_x = do_template('TEMPLATE_LIST', array('_GUID' => '96115a3b168769744b4b69fd2e1e7f6c', 'URL' => '', 'COLOR' => 'red', 'TEMPLATE' => $t, 'LIST' => ''));
                $lis->attach($tpl_x);
            }
        }

        // Prepare all to display...

        $post = new Tempcode();

        /* $lis (the main previews) will be displayed in the main INDEX_SCREEN content */

        // LISTING ADMIN PREVIEWS
        $post->attach(do_template('TEMPLATE_LIST_WRAP', array('_GUID' => '1e847f3c75998f2276765bc0c8ab6b78', 'LI' => $lis_admin, 'TITLE' => do_lang('ADMIN_SCREENS'))));

        // LISTING COMCODE FILES
        $com_li = new Tempcode();
        foreach ($comcode_files as $zone => $pages) {
            if ($zone == 'pages') {
                $zone = "";
            }
            foreach ($pages as $page => $type) {
                if (!is_string($page)) {
                    $page = strval($page);
                }

                $file = $page . '.txt';
                $url = build_url(array('page' => $page), $zone);
                $com_li->attach(do_template('TEMPLATE_LIST', array('_GUID' => '9db6fa9333470137ccf9bb752fd9b19e', 'URL' => $url, 'COLOR' => '', 'TEMPLATE' => $file, 'LIST' => '')));
            }
        }
        $post->attach(do_template('TEMPLATE_LIST_WRAP', array('_GUID' => 'adf69728048cbdbc3a0d9a2e2485a234', 'LI' => $com_li, 'TITLE' => do_lang('COMCODE_PAGES'))));

        // LISTING HTML FILES
        $htm_li = new Tempcode();
        foreach ($html_files as $zone => $pages) {
            foreach ($pages as $page => $type) {
                $file = $page . '.htm';
                $url = build_url(array('page' => $page), $zone);

                $htm_li->attach(do_template('TEMPLATE_LIST', array('_GUID' => '16d1c1c5dc5556254f7a3f28a44fdb52', 'URL' => $url, 'COLOR' => '', 'TEMPLATE' => $file, 'LIST' => '')));
            }
        }
        $post->attach(do_template('TEMPLATE_LIST_WRAP', array('_GUID' => '2220938b443ecdb7d3f2d869665b3a4e', 'LI' => $htm_li, 'TITLE' => do_lang('HTML_PAGES'))));

        return do_template('INDEX_SCREEN', array('_GUID' => '6137f107de679580a6aafe36af427cdd', 'TITLE' => $this->title, 'CONTENT' => $lis, 'POST' => $post, 'PRE' => ''));
    }

    /**
     * Shows the preview of a screen
     *
     * @return Tempcode The UI
     */
    public function view_screen_preview()
    {
        $GLOBALS['SCREEN_TEMPLATE_CALLED'] = '';

        require_code('lorem');

        global $LOREM_AVOID_GLOBALISE;
        $LOREM_AVOID_GLOBALISE = true;

        $template = get_param_string('id');
        $hook = get_param_string('hook');
        $function = get_param_string('function');

        return render_screen_preview($template, $hook, $function);
    }

    /**
     * The UI to run the Tempcode tester.
     *
     * @return Tempcode The UI
     */
    public function tempcode_tester()
    {
        require_javascript('ajax');

        if (get_option('editarea') == '1') {
            attach_to_screen_header(make_string_tempcode('
                <script language="javascript" src="' . get_base_url() . '/data/editarea/edit_area_full.js"></script>
                <script>// <![CDATA[
                editAreaLoader.init({
                    id : "tempcode"
                    ,syntax: "tempcode"
                    ,start_highlight: true
                    ,language: "' . (file_exists(get_file_base() . '/data/editarea/langs/' . strtolower(user_lang())) ? strtolower(user_lang()) : 'en') . '"
                    ,allow_resize: true
                    ,toolbar: "search, go_to_line, fullscreen, |, undo, redo, |, select_font,|, reset_highlight, word_wrap"
                });
                //]]></script>')); // XHTMLXHTML
        }

        return do_template('TEMPCODE_TESTER_SCREEN', array('_GUID' => 'b7ea146fffc0dfdcefcb8e8e0c0168a4', 'TITLE' => $this->title));
    }
}
