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

require_code('crud_module');

/**
 * Module page class.
 */
class Module_cms_catalogues extends Standard_crud_module
{
    public $lang_type = 'CATALOGUE_ENTRY';
    public $select_name = 'ENTRY';
    public $permissions_require = 'mid';
    public $permissions_cat_require = 'catalogues_catalogue';
    public $permissions_cat_name = 'catalogue_name';
    public $permissions_cat_require_b = 'catalogues_category';
    public $permissions_cat_name_b = 'category_id';
    public $user_facing = true;
    public $seo_type = 'catalogue_entry';
    public $catalogue = true;
    public $content_type = 'catalogue_entry';
    public $possibly_some_kind_of_upload = true;
    public $do_preview = null;
    public $menu_label = 'CATALOGUES';
    public $orderer = 'ce_add_date';
    public $table = 'catalogue_entries';
    public $title_is_multi_lang = false;
    public $supports_mass_delete = true;

    public $donext_category_id;
    public $donext_catalogue_name;

    /**
     * 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.
     * @param  boolean $simplified Whether to simplify this down for only a specific catalogue (only applied to cms_catalogues module).
     * @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, $simplified = false)
    {
        if ($member_id === null) {
            $member_id = get_member();
        }

        $ret = array(
            'browse' => array('MANAGE_CATALOGUES', 'menu/rich_content/catalogues/catalogues'),
        );

        if (has_privilege($member_id, 'submit_cat_highrange_content', 'cms_catalogues')) {
            $ret += array(
                'add_category' => array('ADD_CATALOGUE_CATEGORY', 'menu/_generic_admin/add_one_category'),
            );
        }

        if (has_privilege($member_id, 'edit_cat_highrange_content', 'cms_catalogues')) {
            $ret += array(
                'edit_category' => array('EDIT_CATALOGUE_CATEGORY', 'menu/_generic_admin/edit_one_category'),
            );
        }

        if (!$simplified) {
            if (has_privilege($member_id, 'submit_cat_highrange_content', 'cms_catalogues')) {
                $ret += array(
                    'add_catalogue' => array('ADD_CATALOGUE', 'menu/cms/catalogues/add_one_catalogue'),
                );
            }

            if (has_privilege($member_id, 'edit_cat_highrange_content', 'cms_catalogues')) {
                $ret += array(
                    'edit_catalogue' => array('EDIT_CATALOGUE', 'menu/cms/catalogues/edit_one_catalogue'),
                );
            }
        }

        if (has_privilege($member_id, 'mass_import', 'cms_catalogues')) {
            $ret += array(
                'import' => array('IMPORT_CATALOGUE_ENTRIES', 'menu/_generic_admin/import_csv'),
            );
        }

        if ($GLOBALS['FORUM_DRIVER']->is_super_admin($member_id)) {
            $ret += array(
                'export' => array('CATALOGUE_EXPORT', 'menu/_generic_admin/export'),
            );
        }

        $this->cat_crud_module = class_exists('Mx_cms_catalogues_cat') ? new Mx_cms_catalogues_cat() : new Module_cms_catalogues_cat();
        $this->alt_crud_module = class_exists('Mx_cms_catalogues_alt') ? new Mx_cms_catalogues_alt() : new Module_cms_catalogues_alt();

        $ret += parent::get_entry_points();
        unset($ret['add_other']);
        unset($ret['edit_other']);

        if (!$simplified) {
            if ($support_crosslinks) {
                require_code('fields');
                $ret += manage_custom_fields_entry_points('catalogue') + manage_custom_fields_entry_points('catalogue_category');
            }
        }

        return $ret;
    }

    /**
     * Find privileges defined as overridable by this module.
     *
     * @return array A map of privileges that are overridable; privilege to 0 or 1. 0 means "not category overridable". 1 means "category overridable".
     */
    public function get_privilege_overrides()
    {
        require_lang('catalogues');
        return array('view_private_content' => 0, 'mass_import' => 0, 'high_catalogue_entry_timeout' => 1, 'submit_cat_highrange_content' => array(0, 'ADD_CATALOGUE'), 'edit_cat_highrange_content' => array(0, 'EDIT_CATALOGUE'), 'delete_cat_highrange_content' => array(0, 'DELETE_CATALOGUE'), 'submit_cat_midrange_content' => array(0, 'ADD_CATALOGUE_CATEGORY'), 'edit_cat_midrange_content' => array(0, 'EDIT_CATALOGUE_CATEGORY'), 'delete_cat_midrange_content' => array(0, 'DELETE_CATALOGUE_CATEGORY'), 'submit_midrange_content' => array(1, 'ADD_CATALOGUE_ENTRY'), 'bypass_validation_midrange_content' => array(1, 'BYPASS_VALIDATION_CATALOGUE_ENTRY'), 'edit_own_midrange_content' => array(1, 'EDIT_OWN_CATALOGUE_ENTRY'), 'edit_midrange_content' => array(1, 'EDIT_CATALOGUE_ENTRY'), 'delete_own_midrange_content' => array(1, 'DELETE_OWN_CATALOGUE_ENTRY'), 'delete_midrange_content' => array(1, 'DELETE_CATALOGUE_ENTRY'));
    }

    public $title;

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

        $this->cat_crud_module = class_exists('Mx_cms_catalogues_cat') ? new Mx_cms_catalogues_cat() : new Module_cms_catalogues_cat();
        $this->alt_crud_module = class_exists('Mx_cms_catalogues_alt') ? new Mx_cms_catalogues_alt() : new Module_cms_catalogues_alt();
        $GLOBALS['MODULE_CMS_CATALOGUES'] = $this;

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

        require_lang('catalogues');
        require_css('catalogues');

        inform_non_canonical_parameter('parent_id');
        inform_non_canonical_parameter('validated');
        inform_non_canonical_parameter('category_id');
        inform_non_canonical_parameter('category_id_suggest');
        inform_non_canonical_parameter('#^field_.*$#');

        set_helper_panel_tutorial('tut_catalogues');

        if ($type == '_import') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:import', do_lang_tempcode('MANAGE_CATALOGUES')), array('_SELF:_SELF:import', do_lang_tempcode('IMPORT_CATALOGUE_ENTRIES'))));
            breadcrumb_set_self(do_lang_tempcode('DONE'));
        }

        if ($type == 'import' || $type == '_import') {
            $this->title = get_screen_title('CATALOGUE_IMPORT');
        }

        if ($type == 'export') {
            $this->title = get_screen_title('CATALOGUE_EXPORT');

            $GLOBALS['OUTPUT_STREAMING'] = false; // Too complex to do a pre_run for this properly
        }

        switch ($type) {
            case 'add_catalogue':
                $type = 'add_other';
                break;
            case '_add_catalogue':
                $type = '_add_other';
                break;
            case 'edit_catalogue':
                $type = 'edit_other';
                break;
            case '_edit_catalogue':
                $type = '_edit_other';
                break;
            case '__edit_catalogue':
                $type = '__edit_other';
                break;
        }

        $ret = parent::pre_run($top_level, $type);

        if ($type == 'add_other' || $type == '_add_other') {
            $content_type = $this->alt_crud_module->tied_to_content_type(get_param_string('id', null));
            if ($content_type !== null) {
                $this->alt_crud_module->title = get_screen_title('ADD_CATALOGUE_FOR_CONTENT_TYPE', true, array(escape_html($content_type)));
            }
        }

        if ($type == 'edit_other' || $type == '_edit_other') {
            $content_type = $this->alt_crud_module->tied_to_content_type(get_param_string('id', null));
            if ($content_type !== null) {
                $this->alt_crud_module->title = get_screen_title('EDIT_CATALOGUE_FOR_CONTENT_TYPE', true, array(escape_html($content_type)));
            }
        }

        return $ret;
    }

    /**
     * Standard crud_module run_start.
     *
     * @param  ID_TEXT $type The type of module execution
     * @return Tempcode The output of the run
     */
    public function run_start($type)
    {
        if (get_value('disable_cat_cat_perms') === '1') {
            $this->permissions_cat_require_b = null;
            $this->permissions_cat_name_b = null;
            $this->cat_crud_module->permissions_cat_require = null;
            $this->cat_crud_module->permissions_cat_name = null;
        }

        require_lang('fields');
        require_code('catalogues');

        if ($type == 'add_catalogue') {
            require_javascript('ajax');
            $script = find_script('snippet');
            $this->alt_crud_module->javascript .= "
                    var form=document.getElementById('new_field_0_name').form;
                    form.old_submit=form.onsubmit;
                    form.onsubmit=function() {
                        document.getElementById('submit_button').disabled=true;
                        var url='" . addslashes($script) . "?snippet=exists_catalogue&name='+window.encodeURIComponent(form.elements['name'].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;
                    };
            ";
        }

        // Decide what to do
        if ($type == 'browse') {
            return $this->browse();
        }
        if ($type == 'import') {
            return $this->import_catalogue();
        }
        if ($type == '_import') {
            return $this->_import_catalogue();
        }
        if ($type == 'export') {
            return $this->export_catalogue();
        }

        return new Tempcode();
    }

    /**
     * The do-next manager for before content management.
     *
     * @return Tempcode The UI
     */
    public function browse()
    {
        require_code('templates_donext');
        $catalogue_name = get_param_string('catalogue_name', '');
        if ($catalogue_name == '') {
            $extra_map = array();
            $extra_map_2 = array();
        } else {
            require_lang('do_next');
            $extra_map = array('catalogue_name' => $catalogue_name);
            $extra_map_2 = array('id' => $catalogue_name);
            $cat_rows = $GLOBALS['SITE_DB']->query_select('catalogues', array('c_title', 'c_description'), array('c_name' => $catalogue_name), '', 1);
            if (!array_key_exists(0, $cat_rows)) {
                warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue'));
            }
            $cat_title = $cat_rows[0]['c_title'];
        }

        if ((!is_null($catalogue_name)) && ($catalogue_name != '')) {
            $cat_count = $GLOBALS['SITE_DB']->query_select_value('catalogue_categories', 'COUNT(*)', array('c_name' => $catalogue_name));
            $has_categories = ($cat_count != 0);
        } else {
            $has_categories = true;
        }

        require_code('fields');

        return do_next_manager(($catalogue_name != '') ? get_screen_title(escape_html(get_translated_text($cat_title)), false) : get_screen_title('MANAGE_CATALOGUES'), ($catalogue_name != '') ? get_translated_tempcode('catalogues', $cat_rows[0], 'c_description') : comcode_lang_string('DOC_CATALOGUES'),
            array_merge(array(
                (has_privilege(get_member(), 'submit_cat_highrange_content', 'cms_catalogues') && ($catalogue_name == '')) ? array('menu/cms/catalogues/add_one_catalogue', array('_SELF', array_merge($extra_map, array('type' => 'add_catalogue')), '_SELF'), do_lang('ADD_CATALOGUE')) : null,
                has_privilege(get_member(), 'edit_cat_highrange_content', 'cms_catalogues') ? array('menu/cms/catalogues/edit_one_catalogue', array('_SELF', array_merge($extra_map_2, array('type' => ($catalogue_name == '') ? 'edit_catalogue' : '_edit_catalogue')), '_SELF'), do_lang('EDIT_CATALOGUE')) : null,
                has_privilege(get_member(), 'submit_cat_midrange_content', 'cms_catalogues') ? array('menu/_generic_admin/add_one_category', array('_SELF', array_merge($extra_map, array('type' => 'add_category')), '_SELF'), ($catalogue_name != '') ? do_lang('NEXT_ITEM_add_one_category') : do_lang('ADD_CATALOGUE_CATEGORY')) : null,
                has_privilege(get_member(), 'edit_cat_midrange_content', 'cms_catalogues') ? array('menu/_generic_admin/edit_one_category', array('_SELF', array_merge($extra_map, array('type' => 'edit_category')), '_SELF'), ($catalogue_name != '') ? do_lang('NEXT_ITEM_edit_one_category') : do_lang('EDIT_CATALOGUE_CATEGORY')) : null,
                (!$has_categories) ? null : (has_privilege(get_member(), 'submit_midrange_content', 'cms_catalogues') ? array('menu/_generic_admin/add_one', array('_SELF', array_merge($extra_map, array('type' => 'add_entry')), '_SELF'), ($catalogue_name != '') ? do_lang('NEXT_ITEM_add_one') : do_lang('ADD_CATALOGUE_ENTRY')) : null),
                (!$has_categories) ? null : (has_privilege(get_member(), 'edit_midrange_content', 'cms_catalogues') ? array('menu/_generic_admin/edit_one', array('_SELF', array_merge($extra_map, array('type' => 'edit_entry')), '_SELF'), ($catalogue_name != '') ? do_lang('NEXT_ITEM_edit_one') : do_lang('EDIT_CATALOGUE_ENTRY')) : null),
                (!$has_categories) ? null : (has_privilege(get_member(), 'mass_import', 'cms_catalogues') ? array('menu/_generic_admin/import', array('_SELF', array_merge($extra_map, array('type' => 'import')), '_SELF'), do_lang('IMPORT_CATALOGUE_ENTRIES')) : null),
                (!$has_categories) ? null : ($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member()) ? array('menu/_generic_admin/export', array('_SELF', array_merge($extra_map, array('type' => 'export')), '_SELF'), do_lang('EXPORT_CATALOGUE_ENTRIES')) : null),
            ), manage_custom_fields_donext_link('catalogue'), manage_custom_fields_donext_link('catalogue_category')),
            ($catalogue_name != '') ? escape_html(get_translated_text($cat_title)) : do_lang('MANAGE_CATALOGUES')
        );
    }

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

        $current_ordering = get_param_string('sort', 'title ASC');
        if (strpos($current_ordering, ' ') === false) {
            warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
        }
        list($sortable, $sort_order) = explode(' ', $current_ordering, 2);
        $sortables = array(
            'title' => do_lang_tempcode('TITLE'),
            'cc_id' => do_lang_tempcode('CATEGORY'),
            'ce_add_date' => do_lang_tempcode('ADDED'),
            'ce_views' => do_lang_tempcode('COUNT_VIEWS'),
            'ce_submitter' => do_lang_tempcode('metadata:OWNER'),
        );
        if (addon_installed('unvalidated')) {
            $sortables['ce_validated'] = do_lang_tempcode('VALIDATED');
        }
        if (((strtoupper($sort_order) != 'ASC') && (strtoupper($sort_order) != 'DESC')) || (!array_key_exists($sortable, $sortables))) {
            log_hack_attack_and_exit('ORDERBY_HACK');
        }

        $fh = array();
        $fh[] = do_lang_tempcode('TITLE');
        $fh[] = do_lang_tempcode('CATEGORY');
        $fh[] = do_lang_tempcode('ADDED');
        $fh[] = do_lang_tempcode('COUNT_VIEWS');
        $fh[] = do_lang_tempcode('metadata:OWNER');
        if (addon_installed('unvalidated')) {
            $fh[] = protect_from_escaping(do_template('COMCODE_ABBR', array('_GUID' => '4341bfe5e713c94c8fd14ce1cb2e21aa', 'TITLE' => do_lang_tempcode('VALIDATED'), 'CONTENT' => do_lang_tempcode('VALIDATED_SHORT'))));
        }
        $fh[] = do_lang_tempcode('ACTIONS');
        $header_row = results_field_title($fh, $sortables, 'sort', $sortable . ' ' . $sort_order);

        $fields = new Tempcode();

        require_code('form_templates');
        $only_owned = has_privilege(get_member(), 'edit_midrange_content', 'cms_catalogues') ? null : get_member();
        $catalogue_name = get_param_string('catalogue_name');
        list($rows, $max_rows) = $this->get_entry_rows(false, ($current_ordering == 'title ASC' || $current_ordering == 'title DESC') ? 'ce_add_date ASC' : $current_ordering, is_null($only_owned) ? array('c_name' => $catalogue_name) : array('c_name' => $catalogue_name, 'ce_submitter' => $only_owned));
        $_fields = array();
        $cat_titles = array();
        foreach ($rows as $row) {
            $edit_link = build_url($url_map + array('id' => $row['id']), '_SELF');

            $entry_fields = get_catalogue_entry_field_values($catalogue_name, $row['id'], array(0));
            $name = $entry_fields[0]['effective_value']; // 'Name' is value of first field

            $fr = array();
            $fr[] = protect_from_escaping(hyperlink(build_url(array('page' => 'catalogues', 'type' => 'entry', 'id' => $row['id']), get_module_zone('catalogues')), $name, false, true));
            if (array_key_exists($row['cc_id'], $cat_titles)) {
                $cc_title = $cat_titles[$row['cc_id']];
            } else {
                $cc_title = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'cc_title', array('id' => $row['cc_id']));
                $cat_titles[$row['cc_id']] = $cc_title;
            }
            if (!is_null($cc_title)) {
                $fr[] = protect_from_escaping(hyperlink(build_url(array('page' => 'catalogues', 'type' => 'category', 'id' => $row['cc_id']), get_module_zone('catalogues')), get_translated_text($cc_title), false, true));
            } else {
                $fr[] = do_lang('UNKNOWN');
            }
            $fr[] = get_timezoned_date($row['ce_add_date']);
            $fr[] = integer_format($row['ce_views']);
            $username = protect_from_escaping($GLOBALS['FORUM_DRIVER']->member_profile_hyperlink($row['ce_submitter']));
            $fr[] = $username;
            if (addon_installed('unvalidated')) {
                $fr[] = $row['ce_validated'] ? do_lang_tempcode('YES') : do_lang_tempcode('NO');
            }
            $fr[] = protect_from_escaping(hyperlink($edit_link, do_lang_tempcode('EDIT'), false, true, do_lang('EDIT') . ' #' . strval($row['id'])));

            $_fields[] = array('row' => $fr, 'title' => $name);
        }
        if ($current_ordering == 'title ASC' || $current_ordering == 'title DESC') {
            sort_maps_by($_fields, 'title');
            if ($current_ordering == 'title DESC') {
                $_fields = array_reverse($_fields);
            }
        }
        foreach ($_fields as $_fr) {
            $fields->attach(results_entry($_fr['row'], true));
        }

        $is_tree = $GLOBALS['SITE_DB']->query_select_value('catalogues', 'c_is_tree', array('c_name' => $catalogue_name));

        $search_url = build_url(array('page' => 'search', 'id' => 'catalogue_entries', 'catalogue_name' => $catalogue_name), get_module_zone('search'));
        $archive_url = build_url(array('page' => 'catalogues', 'type' => 'index', 'id' => $catalogue_name, 'tree' => $is_tree), get_module_zone('catalogues'));

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

    /**
     * Standard crud_module list function.
     *
     * @return ?array A triple: The tree field (Tempcode), Search URL, Archive URL (null: nothing here)
     */
    public function create_selection_list_ajax_tree()
    {
        $catalogue_name = get_param_string('catalogue_name');

        if ($GLOBALS['SITE_DB']->query_select_value('catalogue_entries', 'COUNT(*)', array('c_name' => $catalogue_name)) == 0) {
            inform_exit(do_lang_tempcode('NO_ENTRIES', 'catalogue_entry'));
        }

        $is_tree = $GLOBALS['SITE_DB']->query_select_value('catalogues', 'c_is_tree', array('c_name' => $catalogue_name));
        if ($is_tree == 0) {
            return null;
        }

        $search_url = build_url(array('page' => 'search', 'id' => 'catalogue_entries', 'catalogue_name' => $catalogue_name), get_module_zone('search'));
        $archive_url = build_url(array('page' => 'catalogues', 'type' => 'index', 'id' => $catalogue_name, 'tree' => $is_tree), get_module_zone('catalogues'));

        $only_owned = has_privilege(get_member(), 'edit_midrange_content', 'cms_catalogues') ? null : get_member();
        $tree = form_input_tree_list(do_lang_tempcode('ENTRY'), '', 'id', null, 'choose_catalogue_entry', array('catalogue_name' => $catalogue_name, 'only_owned' => $only_owned, 'editable_filter' => true), true, null, false, null, has_js() && $this->supports_mass_delete);
        return array($tree, $search_url, $archive_url);
    }

    /**
     * Get Tempcode for a catalogue entry adding/editing form.
     *
     * @param  ?ID_TEXT $catalogue_name The catalogue for the entry (null: detect)
     * @param  ?AUTO_LINK $category_id The category for the entry (null: first)
     * @param  BINARY $validated Whether the entry is validated
     * @param  LONG_TEXT $notes Staff notes
     * @param  ?BINARY $allow_rating Whether rating is allowed (null: decide statistically, based on existing choices)
     * @param  ?SHORT_INTEGER $allow_comments Whether comments are allowed (0=no, 1=yes, 2=review style) (null: decide statistically, based on existing choices)
     * @param  ?BINARY $allow_trackbacks Whether trackbacks are allowed (null: decide statistically, based on existing choices)
     * @param  ?AUTO_LINK $id The ID of the entry (null: not yet added)
     * @return array Tuple: the Tempcode for the visible fields, and the Tempcode for the hidden fields, ..., extra templating details
     */
    public function get_form_fields($catalogue_name = null, $category_id = null, $validated = 1, $notes = '', $allow_rating = null, $allow_comments = null, $allow_trackbacks = null, $id = null)
    {
        list($allow_rating, $allow_comments, $allow_trackbacks) = $this->choose_feedback_fields_statistically($allow_rating, $allow_comments, $allow_trackbacks);

        if (is_null($catalogue_name)) {
            $catalogue_name = get_param_string('catalogue_name');
        }

        $_GET['catalogue_name'] = $catalogue_name; // Useful for referencing in templates etc

        require_code('feedback');
        require_code('form_templates');

        $fields = new Tempcode();

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

        if ((is_null($id)) && (is_null($category_id))) {
            $category_id = get_param_integer('category_id', null);
        }

        $this->add_text = do_lang('CATALOGUE_' . $catalogue_name . '_ADD_TEXT', escape_html(get_base_url()), null, null, null, false);
        $this->edit_text = do_lang('CATALOGUE_' . $catalogue_name . '_EDIT_TEXT', escape_html(get_base_url()), null, null, null, false);

        // Standard fields
        // ===============

        // Category
        if ((is_null($id)) && (is_null($category_id)) && (get_value('no_confirm_url_spec_cats') !== '1')) {
            $category_id = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'MIN(id)', array('c_name' => $catalogue_name, 'cc_parent_id' => null));
            if (is_null($category_id)) {
                $category_id = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'MIN(id)', array('c_name' => $catalogue_name));
            }
        }
        $num_categories = $GLOBALS['SITE_DB']->query_select_value('catalogue_categories', 'COUNT(id)', array('c_name' => $catalogue_name));
        if ((!is_null($category_id)) && ((is_null($id)) && (get_value('no_confirm_url_spec_cats') === '1') || (get_value('no_spec_cat__' . $catalogue_name) === '1')) || ($num_categories == 1)) { // Adding, but defined category ID in URL, and set option saying not to ask for passed categories
            $hidden->attach(form_input_hidden('category_id', strval($category_id)));
        } else {
            if ((is_null($id)) && (is_null($category_id))) {
                $category_id = get_param_integer('category_id_suggest', null); // Less forceful than 'category_id', as may be changed even with 'no_confirm_url_spec_cats' on
            }

            $fields->attach(form_input_tree_list(do_lang_tempcode('CATEGORY'), do_lang_tempcode('DESCRIPTION_CATEGORY_TREE', 'catalogue_category'), 'category_id', null, 'choose_catalogue_category', array('catalogue_name' => $catalogue_name, 'addable_filter' => true), true, is_null($category_id) ? '' : strval($category_id)));
        }

        // Special fields
        // ==============

        if (!is_null($id)) {
            $special_fields = get_catalogue_entry_field_values($catalogue_name, $id);
        } else {
            $special_fields = $GLOBALS['SITE_DB']->query_select('catalogue_fields', array('*'), array('c_name' => $catalogue_name), 'ORDER BY cf_order,' . $GLOBALS['SITE_DB']->translate_field_ref('cf_name'));
        }

        $field_groups = array();

        $field_defaults = array( // We load up these into a map to allow custom add/edit form templating
                                 'CATEGORY_ID' => is_null($category_id) ? '' : strval($category_id),
                                 'VALIDATED' => strval($validated),
                                 'NOTES' => $notes,
                                 'ALLOW_RATING' => strval($allow_rating),
                                 'ALLOW_COMMENTS' => strval($allow_comments),
                                 'ALLOW_TRACKBACKS' => strval($allow_trackbacks),
        );

        require_code('fields');
        foreach ($special_fields as $field_num => $field) {
            $ob = get_fields_hook($field['cf_type']);
            $default = get_param_string('field_' . strval($field['id']), array_key_exists('effective_value_pure', $field) ? $field['effective_value_pure'] : $field['cf_default']);

            $_cf_name = get_translated_text($field['cf_name']);
            $field_cat = '';
            if (strpos($_cf_name, ': ') !== false) {
                $field_cat = substr($_cf_name, 0, strpos($_cf_name, ': '));
                if ($field_cat . ': ' == $_cf_name) {
                    $_cf_name = $field_cat; // Just been pulled out as heading, nothing after ": "
                } else {
                    $_cf_name = substr($_cf_name, strpos($_cf_name, ': ') + 2);
                }
            }
            if (!array_key_exists($field_cat, $field_groups)) {
                $field_groups[$field_cat] = new Tempcode();
            }

            $_cf_description = escape_html(get_translated_text($field['cf_description']));

            $GLOBALS['NO_DEV_MODE_FULLSTOP_CHECK'] = true;
            $result = $ob->get_field_inputter($_cf_name, $_cf_description, $field, $default, is_null($id), !array_key_exists($field_num + 1, $special_fields));

            $GLOBALS['NO_DEV_MODE_FULLSTOP_CHECK'] = false;

            if (is_null($result)) {
                continue;
            }

            if (is_array($result)) {
                $field_groups[$field_cat]->attach($result[0]);
                $hidden->attach($result[1]);
            } else {
                $field_groups[$field_cat]->attach($result);
            }

            if (strpos($field['cf_type'], '_trans') !== false) {
                $this->do_preview = true;
            }

            $field_defaults['FIELD_' . strval($field['id'])] = $default;
            $field_defaults['FIELD_' . strval($field['id']) . '_CHOICES'] = explode('|', $field['cf_default']);

            unset($result);
            unset($ob);
        }

        if (array_key_exists('', $field_groups)) { // Blank prefix must go first
            $field_groups_blank = $field_groups[''];
            unset($field_groups['']);
            $field_groups = array_merge(array($field_groups_blank), $field_groups);
        }
        foreach ($field_groups as $field_group_title => $extra_fields) {
            if (is_integer($field_group_title)) {
                $field_group_title = ($field_group_title == 0) ? '' : strval($field_group_title);
            }

            if ($field_group_title != '') {
                $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => 'a03ec5b2afe5be764bd10694fc401fed', 'TITLE' => $field_group_title)));
            }
            $fields->attach($extra_fields);
        }

        if ($validated == 0) {
            $validated = get_param_integer('validated', 0);
            if (($validated == 1) && (addon_installed('unvalidated'))) {
                attach_message(do_lang_tempcode('WILL_BE_VALIDATED_WHEN_SAVING'), 'inform');
            }
        }
        if ((has_some_cat_privilege(get_member(), 'bypass_validation_' . $this->permissions_require . 'range_content', null, $this->permissions_cat_require_b)) || (has_some_cat_privilege(get_member(), 'bypass_validation_' . $this->permissions_require . 'range_content', null, $this->permissions_cat_require))) {
            if (addon_installed('unvalidated')) {
                if (count($field_groups) != 1) {
                    $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => '17e83c2d6412bddc12ac1873f9ec6092', 'TITLE' => do_lang_tempcode('SETTINGS'))));
                }
                $fields->attach(form_input_tick(do_lang_tempcode('VALIDATED'), do_lang_tempcode($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member()) ? 'DESCRIPTION_VALIDATED_SIMPLE' : 'DESCRIPTION_VALIDATED', 'catalogue_entry'), 'validated', $validated == 1));
            }
        }

        // Metadata
        require_code('seo2');
        $seo_fields = seo_get_fields($this->seo_type, is_null($id) ? null : strval($id), false);
        require_code('feedback2');
        $feedback_fields = feedback_fields($this->content_type, $allow_rating == 1, $allow_comments == 1, $allow_trackbacks == 1, false, $notes, $allow_comments == 2, false, true, false);
        $fields->attach(metadata_get_fields('catalogue_entry', is_null($id) ? null : strval($id), false, null, ($seo_fields->is_empty() && $feedback_fields->is_empty()) ? METADATA_HEADER_YES : METADATA_HEADER_FORCE));
        $fields->attach($seo_fields);
        $fields->attach($feedback_fields);

        if (addon_installed('content_reviews')) {
            $fields->attach(content_review_get_fields('catalogue_entry', is_null($id) ? null : strval($id)));
        }

        if (addon_installed('content_privacy')) {
            require_code('content_privacy2');
            if (is_null($id)) {
                $fields->attach(get_privacy_form_fields('catalogue_entry'));
            } else {
                $fields->attach(get_privacy_form_fields('catalogue_entry', strval($id)));
            }
        }

        $fields2 = new Tempcode();
        if ((!is_null($id)) && (is_ecommerce_catalogue($catalogue_name)) && (!$this->may_delete_this(strval($id)))) {
            $_submitter = $this->get_submitter($id);
            $submitter = $_submitter[0];
            $delete_permission = has_delete_permission($this->permissions_require, get_member(), $submitter, is_null($this->privilege_page_name) ? get_page_name() : $this->privilege_page_name, array($this->permissions_cat_require, is_null($this->permissions_cat_name) ? null : $this->get_cat(strval($id)), $this->permissions_cat_require_b, is_null($this->permissions_cat_name_b) ? null : $this->get_cat_b(strval($id))));
            if ($delete_permission) {
                $fields2->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => 'f38200840366846dd7d9ed769f673657', 'TITLE' => do_lang_tempcode('ACTIONS'), 'SECTION_HIDDEN' => true)));
                $fields2->attach(form_input_tick(do_lang_tempcode('shopping:SHOPPING_FORCE_DELETE'), do_lang_tempcode('shopping:DESCRIPTION_SHOPPING_FORCE_DELETE'), 'force_delete', false));
            }
        }

        return array($fields, $hidden, null, null, false, null, $fields2, null, $field_defaults);
    }

    /**
     * Standard crud_module submitter getter.
     *
     * @param  ID_TEXT $id The entry for which the submitter is sought
     * @return array The submitter, and the time of submission (null submission time implies no known submission time)
     */
    public function get_submitter($id)
    {
        $rows = $GLOBALS['SITE_DB']->query_select('catalogue_entries', array('ce_submitter', 'ce_add_date'), array('id' => intval($id)), '', 1);
        if (!array_key_exists(0, $rows)) {
            return array(null, null);
        }
        return array($rows[0]['ce_submitter'], $rows[0]['ce_add_date']);
    }

    /**
     * Standard crud_module cat getter.
     *
     * @param  ID_TEXT $id The entry for which the cat is sought
     * @return string The cat
     */
    public function get_cat_b($id)
    {
        $temp = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_entries', 'cc_id', array('id' => $id));
        if (is_null($temp)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue_entry'));
        }
        return strval($temp);
    }

    /**
     * Standard crud_module cat getter.
     *
     * @param  ID_TEXT $id The entry for which the cat is sought
     * @return string The cat
     */
    public function get_cat($id)
    {
        $cat = $this->get_cat_b($id);
        return $GLOBALS['SITE_DB']->query_select_value('catalogue_categories', 'c_name', array('id' => intval($cat)));
    }

    /**
     * Standard crud_module edit form filler.
     *
     * @param  ID_TEXT $_id The entry being edited
     * @return array A tuple of lots of info
     */
    public function fill_in_edit_form($_id)
    {
        $id = intval($_id);

        $myrows = $GLOBALS['SITE_DB']->query_select('catalogue_entries', array('*'), array('id' => $id), '', 1);
        if (!array_key_exists(0, $myrows)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue_entry'));
        }
        $myrow = $myrows[0];

        $catalogue_name = $myrow['c_name'];
        if (is_null($catalogue_name)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue_entry'));
        }

        return $this->get_form_fields($catalogue_name, $myrow['cc_id'], $myrow['ce_validated'], $myrow['notes'], $myrow['allow_rating'], $myrow['allow_comments'], $myrow['allow_trackbacks'], $id);
    }

    /**
     * Get a entry-id=>value map of what a submitted catalogue entry form has set
     *
     * @param  ID_TEXT $catalogue_name The name of the catalogue that was used
     * @param  MEMBER $submitter The entry submitter
     * @param  ?AUTO_LINK $editing_id ID of entry being edited (null: not being edited)
     * @return array The map
     */
    public function get_set_field_map($catalogue_name, $submitter, $editing_id = null)
    {
        // Get field values
        $fields = $GLOBALS['SITE_DB']->query_select('catalogue_fields', array('*'), array('c_name' => $catalogue_name), 'ORDER BY cf_order,' . $GLOBALS['SITE_DB']->translate_field_ref('cf_name'));
        $map = array();
        require_code('fields');
        require_code('catalogues');
        foreach ($fields as $field) {
            $object = get_fields_hook($field['cf_type']);

            list(, , $storage_type) = $object->get_field_value_row_bits($field);

            $value = $object->inputted_to_field_value(!is_null($editing_id), $field, 'uploads/catalogues', is_null($editing_id) ? null : _get_catalogue_entry_field($field['id'], $editing_id, $storage_type));
            
            // Required field validation (a standard for all field hooks)
            if (($field['cf_required'] == 1) && (($value == '') || (($value == STRING_MAGIC_NULL) && !fractional_edit()))) {
                warn_exit(do_lang_tempcode('_REQUIRED_NOT_FILLED_IN', $field['cf_name']));
            }
            
            if ((fractional_edit()) && ($value != STRING_MAGIC_NULL)) {
                $rendered = static_evaluate_tempcode($object->render_field_value($field, $value, 0, null, 'catalogue_efv_' . $storage_type, null, 'ce_id', 'cf_id', 'cv_value', $submitter));
                $_POST['field_' . strval($field['id']) . '__altered_rendered_output'] = $rendered;
            }

            $map[$field['id']] = $value;
        }

        return $map;
    }

    /**
     * Standard crud_module add actualiser.
     *
     * @return ID_TEXT The ID of the entry added
     */
    public function add_actualisation()
    {
        require_code('catalogues2');

        $category_id = post_param_integer('category_id');
        $validated = post_param_integer('validated', 0);
        $notes = post_param_string('notes', '');
        $allow_rating = post_param_integer('allow_rating', 0);
        $allow_comments = post_param_integer('allow_comments', 0);
        $allow_trackbacks = post_param_integer('allow_trackbacks', 0);

        $catalogue_name = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'c_name', array('id' => $category_id));
        if (is_null($catalogue_name)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue_category'));
        }

        $map = $this->get_set_field_map($catalogue_name, get_member());

        if ((!is_guest()) && (addon_installed('points'))) {
            $points = $GLOBALS['SITE_DB']->query_select_value('catalogues', 'c_submit_points', array('c_name' => $catalogue_name));
            require_code('points2');
            system_gift_transfer(do_lang('ADD_CATALOGUE_ENTRY'), intval($points), get_member());
        }

        $metadata = actual_metadata_get_fields('catalogue_entry', null);

        $id = actual_add_catalogue_entry($category_id, $validated, $notes, $allow_rating, $allow_comments, $allow_trackbacks, $map, $metadata['add_time'], $metadata['submitter'], null, $metadata['views']);

        set_url_moniker('catalogue_entry', strval($id));

        if (addon_installed('content_privacy')) {
            require_code('content_privacy2');
            list($privacy_level, $additional_access) = read_privacy_fields();
            save_privacy_form_fields('catalogue_entry', strval($id), $privacy_level, $additional_access);
        }

        if (($validated == 1) || (!addon_installed('unvalidated'))) {
            if ((has_actual_page_access(get_modal_user(), 'catalogues')) && ((get_value('disable_cat_cat_perms') === '1') || (has_category_access(get_modal_user(), 'catalogues_category', strval($category_id))) && (has_category_access(get_modal_user(), 'catalogues_catalogue', $catalogue_name)))) {
                $privacy_ok = true;
                if (addon_installed('content_privacy')) {
                    require_code('content_privacy');
                    $privacy_ok = has_privacy_access('catalogue_entry', strval($id), $GLOBALS['FORUM_DRIVER']->get_guest_id());
                }
                if ($privacy_ok) {
                    require_code('activities');
                    $map_copy = $map;
                    $title = array_shift($map_copy);
                    $catalogue_title = get_translated_text($GLOBALS['SITE_DB']->query_select_value('catalogues', 'c_title', array('c_name' => $catalogue_name)));
                    $lang_string = 'catalogues:ACTIVITY_CATALOGUE_' . $catalogue_name . '_ADD';
                    if (is_null(do_lang($lang_string, null, null, null, null, false))) {
                        $lang_string = 'catalogues:ACTIVITY_CATALOGUE_GENERIC_ADD';
                    }
                    syndicate_described_activity($lang_string, $catalogue_title, $title, '', '_SEARCH:catalogues:entry:' . strval($id), '', '', 'catalogues');
                }
            }
        }

        if (addon_installed('content_reviews')) {
            content_review_set('catalogue_entry', strval($id));
        }

        $this->donext_category_id = $category_id;
        $this->donext_catalogue_name = $catalogue_name;

        return strval($id);
    }

    /**
     * Standard crud_module edit actualiser.
     *
     * @param  ID_TEXT $_id The entry being edited
     */
    public function edit_actualisation($_id)
    {
        require_code('catalogues2');

        $id = intval($_id);

        $category_id = post_param_integer('category_id', INTEGER_MAGIC_NULL);
        $validated = post_param_integer('validated', fractional_edit() ? INTEGER_MAGIC_NULL : 0);
        $notes = post_param_string('notes', STRING_MAGIC_NULL);
        $allow_rating = post_param_integer('allow_rating', fractional_edit() ? INTEGER_MAGIC_NULL : 0);
        $allow_comments = post_param_integer('allow_comments', fractional_edit() ? INTEGER_MAGIC_NULL : 0);
        $allow_trackbacks = post_param_integer('allow_trackbacks', fractional_edit() ? INTEGER_MAGIC_NULL : 0);

        $submitter = $GLOBALS['SITE_DB']->query_select_value('catalogue_entries', 'ce_submitter', array('id' => $id));

        $catalogue_name = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'c_name', array('id' => $category_id));
        if (is_null($catalogue_name)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue_category'));
        }
        $map = $this->get_set_field_map($catalogue_name, $submitter, $id);

        if (addon_installed('content_privacy')) {
            require_code('content_privacy2');
            list($privacy_level, $additional_access) = read_privacy_fields();
            save_privacy_form_fields('catalogue_entry', strval($id), $privacy_level, $additional_access);
        }

        if ((has_actual_page_access(get_modal_user(), 'catalogues')) && ((get_value('disable_cat_cat_perms') === '1') || (has_category_access(get_modal_user(), 'catalogues_category', strval($category_id))) && (has_category_access(get_modal_user(), 'catalogues_catalogue', $catalogue_name)))) {
            $privacy_ok = true;
            if (addon_installed('content_privacy')) {
                require_code('content_privacy');
                $privacy_ok = has_privacy_access('catalogue_entry', strval($id), $GLOBALS['FORUM_DRIVER']->get_guest_id());
            }
            if ($privacy_ok) {
                require_code('activities');

                $map_copy = $map;
                $title = array_shift($map_copy);
                $catalogue_title = get_translated_text($GLOBALS['SITE_DB']->query_select_value('catalogues', 'c_title', array('c_name' => $catalogue_name)));

                if (($validated == 1) && ($GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_entries', 'ce_validated', array('id' => $id)) === 0)) { // Just became validated, syndicate as just added
                    $lang_string = ($submitter != get_member()) ? ('catalogues:ACTIVITY_VALIDATE_CATALOGUE_' . $catalogue_name) : ('catalogues:ACTIVITY_CATALOGUE_' . $catalogue_name . '_ADD');
                    if (is_null(do_lang($lang_string, null, null, null, null, false))) {
                        $lang_string = ($submitter != get_member()) ? 'catalogues:ACTIVITY_VALIDATE_CATALOGUE_GENERIC' : 'catalogues:ACTIVITY_CATALOGUE_GENERIC_ADD';
                    }
                    syndicate_described_activity($lang_string, $catalogue_title, $title, '', '_SEARCH:catalogues:entry:' . strval($id), '', '', 'catalogues', 1, $submitter);
                } elseif ($validated == 1) {
                    $lang_string = 'catalogues:ACTIVITY_CATALOGUE_' . $catalogue_name . '_EDIT';
                    if (!is_null(do_lang($lang_string, null, null, null, null, false))) {
                        syndicate_described_activity($lang_string, $catalogue_title, $title, '', '_SEARCH:catalogues:entry:' . strval($id), '', '', 'catalogues', 1, null/*$submitter*/);
                    }
                }
            }
        }

        $metadata = actual_metadata_get_fields('catalogue_entry', strval($id));

        actual_edit_catalogue_entry($id, $category_id, $validated, $notes, $allow_rating, $allow_comments, $allow_trackbacks, $map, post_param_string('meta_keywords', STRING_MAGIC_NULL), post_param_string('meta_description', STRING_MAGIC_NULL), $metadata['edit_time'], $metadata['add_time'], $metadata['views'], $metadata['submitter'], true);

        if (addon_installed('content_reviews')) {
            content_review_set('catalogue_entry', strval($id));
        }

        // Purge support
        if (post_param_integer('force_delete', 0) == 1) {
            $_submitter = $this->get_submitter($id);
            $submitter = $_submitter[0];
            $delete_permission = has_delete_permission($this->permissions_require, get_member(), $submitter, is_null($this->privilege_page_name) ? get_page_name() : $this->privilege_page_name, array($this->permissions_cat_require, is_null($this->permissions_cat_name) ? null : $this->get_cat(strval($id)), $this->permissions_cat_require_b, is_null($this->permissions_cat_name_b) ? null : $this->get_cat_b(strval($id))));
            if ($delete_permission) {
                $start = 0;
                do {
                    $details = $GLOBALS['SITE_DB']->query_select('shopping_order_details', array('order_id', 'p_price'), array('p_id' => $id), '', 1000, $start);
                    foreach ($details as $d) {
                        $GLOBALS['SITE_DB']->query('UPDATE ' . get_table_prefix() . 'shopping_order SET tot_price=tot_price-' . float_to_raw_string($d['p_price']) . ' WHERE id=' . strval($d['order_id']) . ' AND ' . db_string_equal_to('order_status', 'ORDER_STATUS_awaiting_payment'));
                        $GLOBALS['SITE_DB']->query_delete('shopping_order', array('id' => $d['order_id'], 'tot_price' => 0.0), '', 1);
                    }
                    $start += 1000;
                } while (count($details) != 0);
                $GLOBALS['SITE_DB']->query_delete('shopping_order_details', array('p_id' => $id));
                $GLOBALS['SITE_DB']->query_delete('shopping_cart', array('product_id' => $id));
                $this->delete_actualisation($_id);
            }

            unset($_GET['redirect']);
        }

        $this->donext_category_id = $category_id;
        $this->donext_catalogue_name = $catalogue_name;
    }

    /**
     * Standard crud_module delete actualiser.
     *
     * @param  ID_TEXT $_id The entry being deleted
     */
    public function delete_actualisation($_id)
    {
        require_code('catalogues2');

        $id = intval($_id);

        $category_id = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_entries', 'cc_id', array('id' => $id));
        if (is_null($category_id)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue_entry'));
        }

        actual_delete_catalogue_entry($id);

        if (addon_installed('content_privacy')) {
            require_code('content_privacy2');
            delete_privacy_form_fields('catalogue_entry', strval($id));
        }

        $catalogue_name = $GLOBALS['SITE_DB']->query_select_value('catalogue_categories', 'c_name', array('id' => $category_id));
        $this->donext_category_id = $category_id;
        $this->donext_catalogue_name = $catalogue_name;
    }

    /**
     * Standard crud_module delete possibility checker.
     *
     * @param  ID_TEXT $id The entry being potentially deleted
     * @return boolean Whether it may be deleted
     */
    public function may_delete_this($id)
    {
        if (!is_ecommerce_catalogue_entry(intval($id))) {
            return true;
        }
        return
            is_null($GLOBALS['SITE_DB']->query_select_value_if_there('shopping_order_details', 'id', array('p_id' => intval($id), 'p_type' => 'catalogue_items')))
            &&
            is_null($GLOBALS['SITE_DB']->query_select_value_if_there('shopping_cart', 'product_id', array('product_id' => intval($id), 'product_type' => 'catalogue_items')));
    }

    /**
     * The do-next manager for after content management.
     *
     * @param  Tempcode $title The title (output of get_screen_title)
     * @param  Tempcode $description Some description to show, saying what happened
     * @param  ?AUTO_LINK $id The ID of whatever was just handled (null: N/A)
     * @return Tempcode The UI
     */
    public function do_next_manager($title, $description, $id)
    {
        $c_name = $this->donext_catalogue_name;
        $category_id = $this->donext_category_id;

        $is_tree = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogues', 'c_is_tree', array('c_name' => $c_name));

        require_code('templates_donext');
        return do_next_manager($title, $description,
            null,
            null,
            /* TYPED-ORDERED LIST OF 'LINKS'  */
            array('_SELF', array('type' => 'add_entry', 'catalogue_name' => $c_name, 'category_id' => $category_id), '_SELF'), // Add one
            (is_null($id) || (!has_privilege(get_member(), 'edit_own_midrange_content', 'cms_catalogues', array('catalogues_category', $category_id)))) ? null : array('_SELF', array('type' => '_edit_entry', 'id' => $id, 'catalogue_name' => $c_name), '_SELF'), // Edit this
            has_privilege(get_member(), 'edit_own_midrange_content', 'cms_catalogues') ? array('_SELF', array('type' => 'edit_entry', 'catalogue_name' => $c_name), '_SELF') : null, // Edit one
            is_null($id) ? null : array('catalogues', array('type' => 'entry', 'id' => $id), get_module_zone('catalogues')), // View this
            null, // View archive
            null, // Add to category
            has_privilege(get_member(), 'submit_cat_midrange_content', 'cms_catalogues') ? array('_SELF', array('type' => 'add_category', 'catalogue_name' => $c_name), '_SELF') : null, // Add one category
            has_privilege(get_member(), 'edit_own_cat_midrange_content', 'cms_catalogues') ? array('_SELF', array('type' => 'edit_category', 'catalogue_name' => $c_name), '_SELF') : null, // Edit one category

            null, // Edit this category
            null, // View this category
            /* SPECIALLY TYPED 'LINKS' */
            array(),
            array(),
            array(
                has_privilege(get_member(), 'submit_cat_highrange_content', 'cms_catalogues') ? array('menu/cms/catalogues/add_one_catalogue', array('_SELF', array('type' => 'add_catalogue'), '_SELF')) : null,
                has_privilege(get_member(), 'edit_own_cat_highrange_content', 'cms_catalogues') ? array('menu/cms/catalogues/edit_this_catalogue', array('_SELF', array('type' => '_edit_catalogue', 'id' => $c_name), '_SELF')) : null,
                has_privilege(get_member(), 'edit_own_cat_highrange_content', 'cms_catalogues') ? array('menu/cms/catalogues/edit_one_catalogue', array('_SELF', array('type' => 'edit_catalogue'), '_SELF')) : null,
                array('menu/_generic_admin/view_this', array('catalogues', array('type' => 'index', 'id' => $c_name, 'tree' => $is_tree), get_module_zone('catalogues')), do_lang('VIEW_CATALOGUE'))
            ),
            do_lang('MANAGE_CATALOGUES'),
            null,
            null,
            null,
            'catalogue_entry',
            'catalogue_category'
        );
    }

    /**
     * The UI to choose a catalogue to import catalogue entries
     *
     * @return Tempcode The UI
     */
    public function import_catalogue()
    {
        check_privilege('mass_import');

        $catalogue_select = $this->choose_catalogue($this->title);

        if (!is_null($catalogue_select)) {
            return $catalogue_select;
        }

        $catalogue_name = get_param_string('catalogue_name');

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

        $submit_name = do_lang_tempcode('CATALOGUE_IMPORT');

        // Build up form
        $fields = new Tempcode();

        require_code('form_templates');

        $fields->attach(form_input_upload(do_lang_tempcode('UPLOAD'), do_lang_tempcode('CSV_UPLOAD_DESC'), 'file_anytype', true, null, null, true, 'csv,txt'));
        $hidden = new Tempcode();
        handle_max_file_size($hidden);

        $fields->attach(form_input_codename(do_lang_tempcode('CATALOGUE_CSV_IMPORT_KEY_FIELD'), do_lang_tempcode('DESCRIPTION_CATALOGUE_CSV_IMPORT_KEY_FIELD'), 'key_field', '', false));

        $new_handling_options = new Tempcode();
        $new_handling_options->attach(form_input_radio_entry('new_handling', 'add', true, do_lang_tempcode('NEW_HANDLING_ADD')));
        $new_handling_options->attach(form_input_radio_entry('new_handling', 'skip', false, do_lang_tempcode('NEW_HANDLING_SKIP')));
        $fields->attach(form_input_radio(do_lang_tempcode('CATALOGUE_CSV_NEW_HANDLING'), do_lang_tempcode('DESCRIPTION_CATALOGUE_CSV_NEW_HANDLING'), 'new_handling', $new_handling_options));

        $delete_handling_options = new Tempcode();
        $delete_handling_options->attach(form_input_radio_entry('delete_handling', 'delete', false, do_lang_tempcode('DELETE_HANDLING_DELETE')));
        $delete_handling_options->attach(form_input_radio_entry('delete_handling', 'leave', true, do_lang_tempcode('DELETE_HANDLING_LEAVE')));
        $fields->attach(form_input_radio(do_lang_tempcode('CATALOGUE_CSV_DELETE_HANDLING'), do_lang_tempcode('DESCRIPTION_CATALOGUE_CSV_DELETE_HANDLING'), 'delete_handling', $delete_handling_options));

        $update_handling_options = new Tempcode();
        $update_handling_options->attach(form_input_radio_entry('update_handling', 'overwrite', true, do_lang_tempcode('UPDATE_HANDLING_OVERWRITE')));
        $update_handling_options->attach(form_input_radio_entry('update_handling', 'freshen', false, do_lang_tempcode('UPDATE_HANDLING_FRESHEN')));
        $update_handling_options->attach(form_input_radio_entry('update_handling', 'skip', false, do_lang_tempcode('UPDATE_HANDLING_SKIP')));
        $update_handling_options->attach(form_input_radio_entry('update_handling', 'delete', false, do_lang_tempcode('UPDATE_HANDLING_DELETE')));
        $fields->attach(form_input_radio(do_lang_tempcode('CATALOGUE_CSV_UPDATE_HANDLING'), do_lang_tempcode('DESCRIPTION_CATALOGUE_CSV_UPDATE_HANDLING'), 'update_handling', $update_handling_options));

        $javascript = '
            var key_field=document.getElementById(\'key_field\');
            var update_key_settings=function() {
                var has_key=(key_field.value!=\'\');
                key_field.form.elements[\'new_handling\'][0].disabled=!has_key;
                key_field.form.elements[\'new_handling\'][1].disabled=!has_key;
                key_field.form.elements[\'delete_handling\'][0].disabled=!has_key;
                key_field.form.elements[\'delete_handling\'][1].disabled=!has_key;
                key_field.form.elements[\'update_handling\'][0].disabled=!has_key;
                key_field.form.elements[\'update_handling\'][1].disabled=!has_key;
                key_field.form.elements[\'update_handling\'][2].disabled=!has_key;
                key_field.form.elements[\'update_handling\'][3].disabled=!has_key;
            }
            key_field.onchange=update_key_settings;
            update_key_settings();
        ';

        $fields->attach(form_input_codename(do_lang_tempcode('CATALOGUE_CSV_IMPORT_META_KEYWORDS_FIELD'), do_lang_tempcode('DESCRIPTION_CATALOGUE_CSV_IMPORT_META_KEYWORDS_FIELD'), 'meta_keywords_field', '', false));
        $fields->attach(form_input_codename(do_lang_tempcode('CATALOGUE_CSV_IMPORT_META_DESCRIPTION_FIELD'), do_lang_tempcode('DESCRIPTION_CATALOGUE_CSV_IMPORT_META_DESCRIPTION_FIELD'), 'meta_description_field', '', false));
        $fields->attach(form_input_codename(do_lang_tempcode('CATALOGUE_CSV_IMPORT_NOTES_FIELD'), do_lang_tempcode('DESCRIPTION_CATALOGUE_CSV_IMPORT_NOTES_FIELD'), 'notes_field', '', false));

        require_code('feedback2');
        list($allow_rating, $allow_comments, $allow_trackbacks) = $this->choose_feedback_fields_statistically(1, 1, 1);
        $fields->attach(feedback_fields($this->content_type, $allow_rating == 1, $allow_comments == 1, $allow_trackbacks == 1, false, '', $allow_comments == 2, false, false));

        return do_template('FORM_SCREEN', array('_GUID' => '0ad5a822bccb3de8e53fcc47594eb404', 'TITLE' => $this->title, 'JAVASCRIPT' => $javascript, 'TEXT' => do_lang_tempcode('CATALOGUE_IMPORT_TEXT'), 'HIDDEN' => $hidden, 'FIELDS' => $fields, 'SUBMIT_ICON' => 'menu___generic_admin__import', 'SUBMIT_NAME' => $submit_name, 'URL' => $post_url));
    }

    /**
     * Standard actualiser to import catalogue entries
     *
     * @return Tempcode The UI
     */
    public function _import_catalogue()
    {
        check_privilege('mass_import');

        $catalogue_name = get_param_string('catalogue_name');

        // Details about how to handle the import
        $key_field = post_param_string('key_field', '');
        if ($key_field == '') {
            $new_handling = '';
            $delete_handling = '';
            $update_handling = '';
        } else {
            $new_handling = post_param_string('new_handling');
            $delete_handling = post_param_string('delete_handling');
            $update_handling = post_param_string('update_handling');
        }

        $meta_keywords_field = post_param_string('meta_keywords_field', '');
        $meta_description_field = post_param_string('meta_description_field', '');
        $notes_field = post_param_string('notes_field', '');

        $allow_rating = post_param_integer('allow_rating', 0);
        $allow_comments = post_param_integer('allow_comments', 0);
        $allow_trackbacks = post_param_integer('allow_trackbacks', 0);

        // Grab the CSV file
        require_code('uploads');
        $csv_path = mixed();
        if (((is_plupload(true)) && (array_key_exists('file_anytype', $_FILES))) || ((array_key_exists('file_anytype', $_FILES)) && (is_uploaded_file($_FILES['file_anytype']['tmp_name'])))) {
            $csv_path = $_FILES['file_anytype']['tmp_name'];
        }
        if (is_null($csv_path)) {
            warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
        }

        // Fix up the CSV file to have unix style line endings
        $fixed_contents = unixify_line_format(file_get_contents($csv_path));
        require_code('files');
        cms_file_put_contents_safe($csv_path, $fixed_contents, FILE_WRITE_FAILURE_SILENT);

        require_code('tasks');
        return call_user_func_array__long_task(do_lang('CATALOGUE_IMPORT'), $this->title, 'import_catalogue', array($catalogue_name, $key_field, $new_handling, $delete_handling, $update_handling, $meta_keywords_field, $meta_description_field, $notes_field, $allow_rating, $allow_comments, $allow_trackbacks, $csv_path));
    }

    /**
     * The UI to choose a catalogue to export catalogue entries
     *
     * @return Tempcode The UI
     */
    public function export_catalogue()
    {
        if (!$GLOBALS['FORUM_DRIVER']->is_super_admin(get_member())) {
            access_denied('I_ERROR');
        }

        $catalogue_select = $this->choose_catalogue($this->title);

        if (!is_null($catalogue_select)) {
            return $catalogue_select;
        }

        $catalogue_name = get_param_string('catalogue_name');
        $this->_export_catalogue($catalogue_name);
        return new Tempcode();
    }

    /**
     * The actualiser to download a CSV of catalogues.
     *
     * @param ID_TEXT $catalogue_name The name of the catalogue
     * @return Tempcode The UI
     */
    public function _export_catalogue($catalogue_name)
    {
        require_code('tasks');
        return call_user_func_array__long_task(do_lang('CATALOGUE_EXPORT'), $this->title, 'export_catalogue', array($catalogue_name));
    }
}

/**
 * Module page class.
 */
class Module_cms_catalogues_cat extends Standard_crud_module
{
    public $lang_type = 'CATALOGUE_CATEGORY';
    public $select_name = 'NAME';
    public $permissions_require = 'cat_mid';
    public $permission_module = 'catalogues_category';
    public $permissions_cat_require = 'catalogues_catalogue';
    public $permissions_cat_name = 'catalogue_name';
    public $seo_type = 'catalogue_category';
    public $catalogue = true;
    public $content_type = 'catalogue_category';
    public $upload = 'image';
    public $javascript = '
        if (document.getElementById(\'move_days_lower\')) {
            var mt=document.getElementById(\'move_target\');
            var form=mt.form;
            var crf=function() {
                var s=(mt.selectedIndex==0);
                form.elements[\'move_days_lower\'].disabled=s;
                form.elements[\'move_days_higher\'].disabled=s;
            };
            crf();
            mt.onclick=crf;
        }';
    public $menu_label = 'CATALOGUES';
    public $table = 'catalogue_categories';
    public $title_is_multi_lang = false;
    public $orderer = 'cc_title';
    public $is_chained_with_parent_browse = true;

    public $donext_catalogue_name;

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

        $current_ordering = get_param_string('sort', 'cc_title ASC');
        if (strpos($current_ordering, ' ') === false) {
            warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
        }
        list($sortable, $sort_order) = explode(' ', $current_ordering, 2);
        $sortables = array(
            'cc_title' => do_lang_tempcode('TITLE'),
            'cc_order' => do_lang_tempcode('ORDER'),
            'cc_add_date' => do_lang_tempcode('ADDED'),
        );
        if (((strtoupper($sort_order) != 'ASC') && (strtoupper($sort_order) != 'DESC')) || (!array_key_exists($sortable, $sortables))) {
            log_hack_attack_and_exit('ORDERBY_HACK');
        }

        $fh = array(do_lang_tempcode('TITLE'), do_lang_tempcode('ADDED'), do_lang_tempcode('ORDER'));
        $fh[] = do_lang_tempcode('ACTIONS');
        $header_row = results_field_title($fh, $sortables, 'sort', $sortable . ' ' . $sort_order);

        $fields = new Tempcode();

        $catalogue_name = get_param_string('catalogue_name');

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

            $fr = array();
            $fr[] = protect_from_escaping(hyperlink(build_url(array('page' => 'catalogues', 'type' => 'category', 'id' => $row['id']), get_module_zone('catalogues')), get_translated_text($row['cc_title']), false, true));
            $fr[] = get_timezoned_date($row['cc_add_date']);
            $fr[] = ($row['cc_order'] == ORDER_AUTOMATED_CRITERIA) ? do_lang_tempcode('NA_EM') : make_string_tempcode(strval($row['cc_order']));
            $fr[] = protect_from_escaping(hyperlink($edit_link, do_lang_tempcode('EDIT'), false, true, do_lang('EDIT') . ' #' . strval($row['id'])));

            $fields->attach(results_entry($fr, true));
        }

        $is_tree = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogues', 'c_is_tree', array('c_name' => $catalogue_name));

        $search_url = build_url(array('page' => 'search', 'id' => 'catalogue_categories'), get_module_zone('search'));
        $archive_url = build_url(array('page' => 'catalogues', 'type' => 'index', 'id' => $catalogue_name, 'tree' => $is_tree), get_module_zone('catalogues'));

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

    /**
     * Standard crud_module list function.
     *
     * @return ?array A triple: The tree field (Tempcode), Search URL, Archive URL (null: nothing here)
     */
    public function create_selection_list_ajax_tree()
    {
        $catalogue_name = get_param_string('catalogue_name');

        $is_tree = $GLOBALS['SITE_DB']->query_select_value('catalogues', 'c_is_tree', array('c_name' => $catalogue_name));
        if ($is_tree == 0) {
            return null;
        }

        $search_url = build_url(array('page' => 'search', 'id' => 'catalogue_categories', 'catalogue_name' => $catalogue_name), get_module_zone('search'));
        $archive_url = build_url(array('page' => 'catalogues', 'type' => 'index', 'id' => $catalogue_name, 'tree' => $is_tree), get_module_zone('catalogues'));

        $tree = form_input_tree_list(do_lang_tempcode('CODENAME'), '', 'id', null, 'choose_catalogue_category', array('catalogue_name' => $catalogue_name), true);
        return array($tree, $search_url, $archive_url);
    }

    /**
     * Get Tempcode for a catalogue category adding/editing form.
     *
     * @param  ?ID_TEXT $catalogue_name The name of the catalogue the category is in (null: detect)
     * @param  SHORT_TEXT $title The title of the category
     * @param  LONG_TEXT $description Description for the category
     * @param  LONG_TEXT $notes Admin notes
     * @param  ?AUTO_LINK $parent_id The ID of the parent category (null: no parent) (-1: arbitrary default)
     * @param  ?AUTO_LINK $id The ID of this category (null: we're adding, not editing)
     * @param  URLPATH $rep_image The rep-image for the catalogue category
     * @param  integer $move_days_lower The number of days before expiry (lower limit)
     * @param  integer $move_days_higher The number of days before expiry (higher limit)
     * @param  ?AUTO_LINK $move_target The expiry category (null: do not expire)
     * @param  ?integer $order The order (null: auto-calculate, for new category)
     * @return array A pair: the Tempcode for the visible fields, and the Tempcode for the hidden fields
     */
    public function get_form_fields($catalogue_name = null, $title = '', $description = '', $notes = '', $parent_id = -1, $id = null, $rep_image = '', $move_days_lower = 30, $move_days_higher = 60, $move_target = null, $order = null)
    {
        if (is_null($catalogue_name)) {
            $catalogue_name = get_param_string('catalogue_name', is_null($id) ? false : $GLOBALS['SITE_DB']->query_select_value('catalogues_categories', 'c_name', array('id' => $id)));
        }

        if ($parent_id == -1) {
            $parent_id = get_param_integer('parent_id', -1);
        }

        $fields = new Tempcode(); // Not the fields in a category (no such thing: fields are in catalogues) - the HTML form fields to input the details for a category
        $hidden = new Tempcode();

        require_code('form_templates');

        $hidden->attach(form_input_hidden('catalogue_name', $catalogue_name));

        $fields->attach(form_input_line(do_lang_tempcode('TITLE'), do_lang_tempcode('DESCRIPTION_TITLE'), 'title', $title, true));

        $fields->attach(form_input_text_comcode(do_lang_tempcode('DESCRIPTION'), do_lang_tempcode('DESCRIPTION_DESCRIPTION'), 'description', $description, false));

        if (get_option('enable_staff_notes') == '1') {
            $fields->attach(form_input_text(do_lang_tempcode('NOTES'), do_lang_tempcode('DESCRIPTION_NOTES'), 'notes', $notes, false));
        }

        $fields->attach(form_input_upload_multi_source(do_lang_tempcode('REPRESENTATIVE_IMAGE'), do_lang_tempcode('DESCRIPTION_REPRESENTATIVE_IMAGE', 'catalogue_category'), $hidden, 'image', null, false, $rep_image));

        // Is the catalogue a tree?
        $is_tree = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogues', 'c_is_tree', array('c_name' => $catalogue_name));
        if (is_null($is_tree)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue'));
        }
        if (($is_tree == 1) && (!is_null($parent_id))) {
            $fields->attach(form_input_tree_list(do_lang_tempcode('PARENT'), do_lang_tempcode('DESCRIPTION_PARENT', 'catalogue_category'), 'parent_id', null, 'choose_catalogue_category', array('catalogue_name' => $catalogue_name), true, ((is_null($parent_id)) || ($parent_id == -1)) ? '' : strval($parent_id)));
        }

        if ($is_tree) {
            $hidden->attach(form_input_hidden('order', strval(($order === null) ? ORDER_AUTOMATED_CRITERIA : $order)));
        } else {
            $max = $GLOBALS['SITE_DB']->query_select_value('catalogue_categories', 'MAX(cc_order)', array('c_name' => $catalogue_name), 'AND cc_order<>' . strval(ORDER_AUTOMATED_CRITERIA));
            if (is_null($max)) {
                $max = 0;
            }
            $total = $GLOBALS['SITE_DB']->query_select_value('catalogue_categories', 'COUNT(*)', array('c_name' => $catalogue_name));
            $num_automated_now = $GLOBALS['SITE_DB']->query_select_value('catalogue_categories', 'COUNT(cc_order)', null, 'WHERE ' . db_string_equal_to('c_name', $catalogue_name) . ' AND cc_order=' . strval(ORDER_AUTOMATED_CRITERIA));
            $fields->attach(get_order_field('catalogue_category', null, $order, $max, $total, 'order', null, $num_automated_now));
        }

        if (cron_installed()) {
            $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => '745236e628a4d3da5355f07874433600', 'SECTION_HIDDEN' => is_null($move_target), 'TITLE' => do_lang_tempcode('CLASSIFIED_ADS'))));
            $fields->attach(form_input_tree_list(do_lang_tempcode('EXPIRY_MOVE_TARGET'), do_lang_tempcode('DESCRIPTION_EXPIRY_MOVE_TARGET'), 'move_target', null, 'choose_catalogue_category', array('catalogue_name' => $catalogue_name), false, is_null($move_target) ? null : strval($move_target)));
            $fields->attach(form_input_integer(do_lang_tempcode('EXPIRY_MOVE_DAYS_LOWER'), do_lang_tempcode('DESCRIPTION_EXPIRY_MOVE_DAYS_LOWER'), 'move_days_lower', $move_days_lower, true));
            $fields->attach(form_input_integer(do_lang_tempcode('EXPIRY_MOVE_DAYS_HIGHER'), do_lang_tempcode('DESCRIPTION_EXPIRY_MOVE_DAYS_HIGHER'), 'move_days_higher', $move_days_higher, true));
        }

        $fields->attach(metadata_get_fields('catalogue_category', is_null($id) ? null : strval($id)));
        require_code('seo2');
        $fields->attach(seo_get_fields($this->seo_type, is_null($id) ? null : strval($id), false));

        if (addon_installed('content_reviews')) {
            $fields->attach(content_review_get_fields('catalogue_category', is_null($id) ? null : strval($id)));
        }

        // Permissions
        if (get_value('disable_cat_cat_perms') !== '1') {
            $fields->attach($this->get_permission_fields(is_null($id) ? '' : strval($id), null, is_null($id)));
        }

        return array($fields, $hidden);
    }

    /**
     * Standard crud_module cat getter.
     *
     * @param  ID_TEXT $id The entry being edited
     * @return string The cat
     */
    public function get_cat($id)
    {
        $c_name = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'c_name', array('id' => intval($id)));
        if (is_null($c_name)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue_category'));
        }
        return $c_name;
    }

    /**
     * Standard crud_module edit form filler.
     *
     * @param  ID_TEXT $_id The entry being edited
     * @return array A tuple of lots of info
     */
    public function fill_in_edit_form($_id)
    {
        $category_id = intval($_id);

        $catalogue_name = get_param_string('catalogue_name', $GLOBALS['SITE_DB']->query_select_value('catalogue_categories', 'c_name', array('id' => $category_id)));

        $rows = $GLOBALS['SITE_DB']->query_select('catalogue_categories', array('*'), array('id' => $category_id), '', 1);
        if (!array_key_exists(0, $rows)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue_category'));
        }
        $myrow = $rows[0];

        return $this->get_form_fields($catalogue_name, get_translated_text($myrow['cc_title']), get_translated_text($myrow['cc_description']), $myrow['cc_notes'], $myrow['cc_parent_id'], $category_id, $myrow['rep_image'], $myrow['cc_move_days_lower'], $myrow['cc_move_days_higher'], $myrow['cc_move_target'], $myrow['cc_order']);
    }

    /**
     * Standard crud_module delete possibility checker.
     *
     * @param  ID_TEXT $id The entry being potentially deleted
     * @return boolean Whether it may be deleted
     */
    public function may_delete_this($id)
    {
        $cat = $GLOBALS['SITE_DB']->query_select('catalogue_categories cc LEFT JOIN ' . get_table_prefix() . 'catalogues c ON c.c_name=cc.c_name', array('cc_parent_id', 'c_is_tree'), array('id' => intval($id)), '', 1);
        if (!array_key_exists(0, $cat)) {
            return true;
        }
        return ($cat[0]['c_is_tree'] == 0) || (!is_null($cat[0]['cc_parent_id']));
    }

    /**
     * Standard crud_module add actualiser.
     *
     * @return AUTO_LINK The ID of what was added
     */
    public function add_actualisation()
    {
        require_code('catalogues2');

        $catalogue_name = post_param_string('catalogue_name');

        $title = post_param_string('title');
        $description = post_param_string('description');
        $notes = post_param_string('notes', '');
        $parent_id = post_param_integer('parent_id', null);
        require_code('themes2');
        $rep_image = resize_rep_image(post_param_image('image', 'uploads/repimages', null, false));

        $move_days_lower = post_param_integer('move_days_lower', 30);
        $move_days_higher = post_param_integer('move_days_higher', 60);
        $move_target = post_param_integer('move_target', null);
        if (!is_null($move_target)) {
            if (!has_submit_permission('mid', get_member(), get_ip_address(), 'cms_catalogues', array('catalogues_catalogue', $catalogue_name) + ((get_value('disable_cat_cat_perms') !== '1') ? array('catalogues_category', $move_target) : array()))) {
                access_denied('CATEGORY_ACCESS');
            }
        }

        $metadata = actual_metadata_get_fields('catalogue_category', null);

        $category_id = actual_add_catalogue_category($catalogue_name, $title, $description, $notes, $parent_id, $rep_image, $move_days_lower, $move_days_higher, $move_target, post_param_order_field(), $metadata['add_time']);

        set_url_moniker('catalogue_category', strval($category_id));

        if (get_value('disable_cat_cat_perms') !== '1') {
            $this->set_permissions(strval($category_id));
        }

        if (addon_installed('content_reviews')) {
            content_review_set('catalogue_category', strval($category_id));
        }

        $this->donext_category_id = $category_id;
        $this->donext_catalogue_name = $catalogue_name;

        return $category_id;
    }

    /**
     * Standard crud_module edit actualiser.
     *
     * @param  ID_TEXT $_id The entry being edited
     */
    public function edit_actualisation($_id)
    {
        require_code('catalogues2');

        $category_id = intval($_id);

        $catalogue_name = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'c_name', array('id' => $category_id));
        if (is_null($catalogue_name)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue_category'));
        }

        $title = post_param_string('title');
        $description = post_param_string('description', STRING_MAGIC_NULL);
        $notes = post_param_string('notes', STRING_MAGIC_NULL);
        $parent_id = post_param_integer('parent_id', fractional_edit() ? INTEGER_MAGIC_NULL : null/*may be non-tree catalogue or root node*/);

        $move_days_lower = post_param_integer('move_days_lower', fractional_edit() ? INTEGER_MAGIC_NULL : 30/*may be CRON disabled*/);
        $move_days_higher = post_param_integer('move_days_higher', fractional_edit() ? INTEGER_MAGIC_NULL : 60/*may be CRON disabled*/);
        $move_target = post_param_integer('move_target', fractional_edit() ? INTEGER_MAGIC_NULL : null);
        if ((!is_null($move_target)) && ($move_target != INTEGER_MAGIC_NULL)) {
            if (!has_submit_permission('mid', get_member(), get_ip_address(), 'cms_catalogues', array('catalogues_catalogue', $catalogue_name) + ((get_value('disable_cat_cat_perms') !== '1') ? array('catalogues_category', $move_target) : array()))) {
                access_denied('CATEGORY_ACCESS');
            }
        }

        if (!fractional_edit()) {
            require_code('themes2');
            $rep_image = resize_rep_image(post_param_image('image', 'uploads/repimages', null, false, true));
        } else {
            $rep_image = STRING_MAGIC_NULL;
        }

        $metadata = actual_metadata_get_fields('catalogue_category', strval($category_id));

        actual_edit_catalogue_category($category_id, $title, $description, $notes, $parent_id, post_param_string('meta_keywords', STRING_MAGIC_NULL), post_param_string('meta_description', STRING_MAGIC_NULL), $rep_image, $move_days_lower, $move_days_higher, $move_target, fractional_edit() ? INTEGER_MAGIC_NULL : post_param_order_field(), $metadata['add_time']);
        if (!fractional_edit()) {
            if (get_value('disable_cat_cat_perms') !== '1') {
                $this->set_permissions(strval($category_id));
            }
        }

        if (addon_installed('content_reviews')) {
            content_review_set('catalogue_category', strval($category_id));
        }

        $this->donext_category_id = $category_id;
        $this->donext_catalogue_name = $catalogue_name;
    }

    /**
     * Standard crud_module delete actualiser.
     *
     * @param  ID_TEXT $id The entry being deleted
     */
    public function delete_actualisation($id)
    {
        require_code('catalogues2');

        $catalogue_name = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'c_name', array('id' => $id));
        if (is_null($catalogue_name)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue_category'));
        }

        actual_delete_catalogue_category(intval($id));

        $this->donext_catalogue_name = $catalogue_name;
    }

    /**
     * The do-next manager for after catalogue content management.
     *
     * @param  Tempcode $title The title (output of get_screen_title)
     * @param  Tempcode $description Some description to show, saying what happened
     * @param  ?AUTO_LINK $id The ID of whatever catalogue category was just handled (null: deleted)
     * @return Tempcode The UI
     */
    public function do_next_manager($title, $description, $id)
    {
        $catalogue_name = $this->donext_catalogue_name;

        $is_tree = $GLOBALS['SITE_DB']->query_select_value('catalogues', 'c_is_tree', array('c_name' => $catalogue_name));

        require_code('templates_donext');
        return do_next_manager($title, $description,
            null,
            null,
            /* TYPED-ORDERED LIST OF 'LINKS'  */
            array('_SELF', array('type' => 'add_entry', 'category_id' => $id, 'catalogue_name' => $catalogue_name), '_SELF'), // Add one
            null, // Edit this
            has_privilege(get_member(), 'edit_own_midrange_content', 'cms_catalogues') ? array('_SELF', array('type' => 'edit_entry', 'catalogue_name' => $catalogue_name), '_SELF') : null, // Edit one
            null, // View this
            null, // View archive
            null, // Add to category
            has_privilege(get_member(), 'submit_cat_midrange_content', 'cms_catalogues') ? array('_SELF', array('type' => 'add_category', 'catalogue_name' => $catalogue_name), '_SELF') : null, // Add one category
            has_privilege(get_member(), 'edit_own_cat_midrange_content', 'cms_catalogues') ? array('_SELF', array('type' => 'edit_category', 'catalogue_name' => $catalogue_name), '_SELF') : null, // Edit one category
            (is_null($id) || (!has_privilege(get_member(), 'edit_own_cat_midrange_content', 'cms_catalogues'))) ? null : array('_SELF', array('type' => '_edit_category', 'id' => $id, 'catalogue_name' => $catalogue_name), '_SELF'), // Edit this category
            is_null($id) ? null : array('catalogues', array('type' => 'category', 'id' => $id), get_module_zone('catalogues')), // View this category

            /* SPECIALLY TYPED 'LINKS' */
            array(), array(),
            array(
                has_privilege(get_member(), 'submit_cat_highrange_content', 'cms_catalogues') ? array('menu/cms/catalogues/add_one_catalogue', array('_SELF', array('type' => 'add_catalogue'), '_SELF')) : null,
                has_privilege(get_member(), 'edit_own_cat_highrange_content', 'cms_catalogues') ? array('menu/cms/catalogues/edit_this_catalogue', array('_SELF', array('type' => '_edit_catalogue', 'id' => $catalogue_name), '_SELF')) : null,
                has_privilege(get_member(), 'edit_own_cat_highrange_content', 'cms_catalogues') ? array('menu/cms/catalogues/edit_one_catalogue', array('_SELF', array('type' => 'edit_catalogue'), '_SELF')) : null,
                array('menu/_generic_admin/view_this', array('catalogues', array('type' => 'index', 'id' => $catalogue_name, 'tree' => $is_tree), get_module_zone('catalogues')), do_lang('INDEX'))
            ),
            do_lang('MANAGE_CATALOGUES'),
            null,
            null,
            null,
            'catalogue_entry',
            'catalogue_category'
        );
    }
}

/**
 * Module page class.
 */
class Module_cms_catalogues_alt extends Standard_crud_module
{
    public $lang_type = 'CATALOGUE';
    public $select_name = 'CATALOGUE';
    public $select_name_description = 'DESCRIPTION_CATALOGUE';
    public $permissions_require = 'cat_high';
    public $permission_module = 'catalogues_catalogue';
    public $non_integer_id = true;
    public $content_type = 'catalogue';
    public $is_tree_catalogue = false; // Set for usage by do-next-manager
    public $menu_label = 'CATALOGUES';
    public $table = 'catalogues';
    public $javascript = "var fn=document.getElementById('title'); if (fn) { var form=fn.form; fn.onchange=function() { if ((form.elements['name']) && (form.elements['name'].value=='')) form.elements['name'].value=fn.value.toLowerCase().replace(/[^\w\-\\u0080-\\uFFFF]/g,'_').replace(/\_+\$/,'').substr(0,80); }; }";
    public $is_chained_with_parent_browse = true;

    /**
     * Standard crud_module list function.
     *
     * @return Tempcode The selection list
     */
    public function create_selection_list_entries()
    {
        return create_selection_list_catalogues();
    }

    /**
     * Standard aed_module delete possibility checker.
     *
     * @param  ID_TEXT $id The entry being potentially deleted
     * @return boolean Whether it may be deleted
     */
    public function may_delete_this($id)
    {
        if (substr($id, 0, 1) == '_') {
            return false;
        }
        return true;
    }

    /**
     * Find what content type this is for.
     *
     * @param  ?ID_TEXT $name The catalogue name (null: n/a)
     * @return ?ID_TEXT The content type (null: none)
     */
    public function tied_to_content_type($name)
    {
        if (is_null($name)) {
            return null;
        }

        $rem_name = substr($name, 1);
        $tied_to_content_type = (substr($name, 0, 1) == '_') && ((file_exists(get_file_base() . '/sources_custom/hooks/systems/content_meta_aware/' . $rem_name . '.php')) || (file_exists(get_file_base() . '/sources/hooks/systems/content_meta_aware/' . $rem_name . '.php')));
        if ($tied_to_content_type) {
            return $rem_name;
        }
        return null;
    }

    /**
     * Get Tempcode for a catalogue adding/editing form.
     *
     * @param  ID_TEXT $name The name of the catalogue
     * @param  SHORT_TEXT $title The human readable name/title of the catalogue
     * @param  LONG_TEXT $description The description
     * @param  SHORT_INTEGER $display_type The display type
     * @param  BINARY $is_tree Whether the catalogue uses a hierarchy
     * @param  LONG_TEXT $notes Admin notes
     * @param  integer $submit_points How many points are given to a member that submits to the catalogue
     * @param  BINARY $ecommerce Whether the catalogue is an eCommerce catalogue
     * @param  ID_TEXT $send_view_reports How to send view reports
     * @set    never daily weekly monthly quarterly
     * @param  ?integer $default_review_freq Default review frequency for catalogue entries (null: none)
     * @return array A tuple: the Tempcode for the visible fields, and the Tempcode for the hidden fields, ..., and action fields
     */
    public function get_form_fields($name = '', $title = '', $description = '', $display_type = 0, $is_tree = 1, $notes = '', $submit_points = 0, $ecommerce = 0, $send_view_reports = 'never', $default_review_freq = null)
    {
        $fields = new Tempcode();
        $hidden = new Tempcode();
        require_code('form_templates');

        if ($name == '') {
            $name = get_param_string('id', '');
        }
        $content_type = $this->tied_to_content_type($name);
        if ($content_type !== null) {
            require_code('content');
            $ob = get_content_object($content_type);
            $info = $ob->info();
            if ($info === null) {
                fatal_exit(do_lang_tempcode('INTERNAL_ERROR'));
            }

            $title = do_lang('CUSTOM_FIELDS_FOR', do_lang($info['content_type_label']));

            $hidden->attach(form_input_hidden('title', $title));
            $hidden->attach(form_input_hidden('name', $name));
            $hidden->attach(form_input_hidden('description', ''));
            $hidden->attach(form_input_hidden('notes', ''));
            $hidden->attach(form_input_hidden('auto_fill', ''));
            $hidden->attach(form_input_hidden('display_type', '0'));
            $hidden->attach(form_input_hidden('submit_points', '0'));
            $hidden->attach(form_input_hidden('send_view_reports', 'never'));

            attach_message(do_lang_tempcode('EDITING_CUSTOM_FIELDS_HELP', do_lang_tempcode($info['content_type_label'])), 'inform', true);

            $actions = new Tempcode();
        } else {
            $fields->attach(form_input_line(do_lang_tempcode('TITLE'), do_lang_tempcode('DESCRIPTION_TITLE'), 'title', $title, true));
            $complex_rename = ($name == 'products');
            $catalogue_name_field = form_input_codename(do_lang_tempcode('CODENAME'), do_lang_tempcode($complex_rename ? 'DESCRIPTION_CODENAME_SHOULDNT' : 'DESCRIPTION_CODENAME'), 'name', $name, true);
            if (!$complex_rename) {
                $fields->attach($catalogue_name_field);
            }
            $fields->attach(form_input_text_comcode(do_lang_tempcode('DESCRIPTION'), do_lang_tempcode('DESCRIPTION_CATALOGUE_DESCRIPTION'), 'description', $description, false));

            $display_types = new Tempcode();
            foreach (array(C_DT_FIELDMAPS => 'DT_FIELDMAPS', C_DT_TITLELIST => 'DT_TITLELIST', C_DT_TABULAR => 'DT_TABULAR', C_DT_GRID => 'DT_GRID') as $_display_type => $display_type_str) {
                $display_types->attach(form_input_list_entry(strval($_display_type), $display_type == $_display_type, do_lang_tempcode($display_type_str)));
            }
            $fields->attach(form_input_list(do_lang_tempcode('DISPLAY_TYPE'), do_lang_tempcode('DESCRIPTION_DISPLAY_TYPE'), 'display_type', $display_types));

            $fields->attach(form_input_tick(do_lang_tempcode('IS_TREE'), do_lang_tempcode('DESCRIPTION_IS_TREE'), 'is_tree', $is_tree == 1));
            if ($name == '') {
                $fields->attach(form_input_line(do_lang_tempcode('AUTO_FILL'), do_lang_tempcode('DESCRIPTION_AUTO_FILL'), 'auto_fill', '', false, null, pow(2, 31) - 1));
            }

            $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => 'd7f7e0da078bdfaab0b3387d200d57a4', 'SECTION_HIDDEN' => $notes == '' && $submit_points == 0 && $send_view_reports == 'never', 'TITLE' => do_lang_tempcode('ADVANCED'))));

            if ($complex_rename) {
                $fields->attach($catalogue_name_field);
            }

            if (addon_installed('shopping')) {
                if ($ecommerce == 1) {
                    if (get_forum_type() != 'cns') {
                        warn_exit(do_lang_tempcode('NO_CNS'));
                    }
                }

                $fields->attach(form_input_tick(do_lang_tempcode('CAT_ECOMMERCE'), do_lang_tempcode('DESCRIPTION_CAT_ECOMMERCE'), 'ecommerce', $ecommerce == 1));
            }

            if (get_option('enable_staff_notes') == '1') {
                $fields->attach(form_input_text(do_lang_tempcode('NOTES'), do_lang_tempcode('DESCRIPTION_NOTES'), 'notes', $notes, false));
            }
            if (addon_installed('points')) {
                $fields->attach(form_input_integer(do_lang_tempcode('SUBMIT_POINTS'), do_lang_tempcode('DESCRIPTION_SUBMIT_POINTS'), 'submit_points', $submit_points, false));
            }

            $view_report_types = new Tempcode();
            $view_report_types->attach(form_input_list_entry('never', $send_view_reports == 'never', do_lang_tempcode('VR_NEVER')));
            $view_report_types->attach(form_input_list_entry('daily', $send_view_reports == 'daily', do_lang_tempcode('VR_DAILY')));
            $view_report_types->attach(form_input_list_entry('weekly', $send_view_reports == 'weekly', do_lang_tempcode('VR_WEEKLY')));
            $view_report_types->attach(form_input_list_entry('monthly', $send_view_reports == 'monthly', do_lang_tempcode('VR_MONTHLY')));
            $view_report_types->attach(form_input_list_entry('quarterly', $send_view_reports == 'quarterly', do_lang_tempcode('VR_QUARTERLY')));
            $fields->attach(form_input_list(do_lang_tempcode('VIEW_REPORTS'), do_lang_tempcode('DESCRIPTION_VIEW_REPORTS'), 'send_view_reports', $view_report_types));

            $fields->attach(metadata_get_fields('catalogue', ($name == '') ? null : $name));

            if (addon_installed('content_reviews')) {
                $fields->attach(content_review_get_fields('catalogue', ($name == '') ? null : $name));
            }

            // Permissions
            $fields->attach($this->get_permission_fields($name, null, ($name == '')));

            $actions = new Tempcode();
            if (($name != '') && (get_value('disable_cat_cat_perms') !== '1')) {
                $actions->attach(form_input_tick(do_lang_tempcode('RESET_CATEGORY_PERMISSIONS'), do_lang_tempcode('DESCRIPTION_RESET_CATEGORY_PERMISSIONS'), 'reset_category_permissions', false));
            }
        }

        if (addon_installed('content_reviews')) {
            require_lang('content_reviews');
            $fields->attach(form_input_integer(do_lang_tempcode('REVIEW_FREQ'), do_lang_tempcode('DESCRIPTION_CATALOGUE_REVIEW_FREQ'), 'default_review_freq', $default_review_freq, false));
        }

        return array($fields, $hidden, null, null, false, null, $actions);
    }

    /**
     * Get Tempcode for a catalogue field adding/editing form (many of these are put together to add/edit a single catalogue!).
     *
     * @param  boolean $first_field Whether this is the first field of the entry fields
     * @param  integer $num_fields_to_show The number of fields that will be on the screen
     * @param  string $prefix The prefix the field input fields are given (e.g. new1_)
     * @param  integer $order The order of the field relative to the other fields
     * @param  SHORT_TEXT $name The name of the field
     * @param  LONG_TEXT $description Description for the field
     * @param  ID_TEXT $type The field type
     * @param  BINARY $defines_order Whether the field defines entry ordering
     * @param  BINARY $visible Whether the field is searchable
     * @param  BINARY $searchable Whether the field is visible when an entry is viewed
     * @param  SHORT_TEXT $default Default value for the field
     * @param  BINARY $required Whether the field is required
     * @param  BINARY $put_in_category Whether the field is to be shown in category views (not applicable for the list display type)
     * @param  BINARY $put_in_search Whether the field is to be shown in search views (not applicable for the list display type)
     * @param  SHORT_TEXT $options Field options
     * @return array A pair: the Tempcode for the visible fields, and the Tempcode for the hidden fields
     */
    public function get_field_fields($first_field, $num_fields_to_show, $prefix, $order, $name = '', $description = '', $type = 'short_text', $defines_order = 0, $visible = 1, $searchable = 1, $default = '', $required = 0, $put_in_category = 1, $put_in_search = 1, $options = '')
    {
        $fields = new Tempcode();
        $hidden = new Tempcode();

        require_code('form_templates');
        $fields->attach(form_input_line(do_lang_tempcode('NAME'), do_lang_tempcode('DESCRIPTION_FIELD_NAME'), $prefix . 'name', $name, ($name != '') || $first_field)); // If this is gonna be a new field that might not be filled in, don't make them fill it in
        $fields->attach(form_input_line(do_lang_tempcode('DESCRIPTION'), do_lang_tempcode('DESCRIPTION_FIELD_DESCRIPTION'), $prefix . 'description', $description, false));

        require_code('fields');
        require_lang('fields');

        $type_list = create_selection_list_field_type($type, $name != '');

        $fields->attach(form_input_list(do_lang_tempcode('TYPE'), do_lang_tempcode(($name == '') ? 'DESCRIPTION_FIELD_TYPE_FIRST_TIME' : 'DESCRIPTION_FIELD_TYPE'), $prefix . 'type', $type_list));

        $fields->attach(form_input_line(do_lang_tempcode('FIELD_OPTIONS'), do_lang_tempcode('DESCRIPTION_FIELD_OPTIONS', escape_html(get_tutorial_url('tut_fields'))), $prefix . 'options', $options, false));

        $fields->attach(form_input_line(do_lang_tempcode('DEFAULT_VALUE'), do_lang_tempcode('DESCRIPTION_FIELD_DEFAULT'), $prefix . 'default', $default, false, null, pow(2, 31) - 1));

        //$order_list = new Tempcode();
        $order_list = '';
        for ($i = 0; $i < $num_fields_to_show; $i++) {
            $order_title = integer_format($i + 1);
            $order_selected = ($i == $order);
            if (($i == 0) && (substr(get_param_string('id', ''), 0, 1) != '_')) {
                $order_title .= do_lang('NEW_FIELD_TITLE');
            }
            //$order_list->attach(form_input_list_entry(strval($i), $order_selected, $order_title));
            $order_list .= '<option value="' . strval($i) . '"' . ($order_selected ? ' selected="selected"' : '') . '>' . escape_html($order_title) . '</option>'; // XHTMLXHTML
        }
        $fields->attach(form_input_list(do_lang_tempcode('ORDER'), do_lang_tempcode('DESCRIPTION_FIELD_ORDER_CLEVER', do_lang_tempcode('CATALOGUE_FIELD')), $prefix . 'order', make_string_tempcode($order_list)));

        // Defines order?
        $radios = new Tempcode();
        $radios->attach(form_input_radio_entry($prefix . 'defines_order', '0', $defines_order == 0, do_lang_tempcode('NO')));
        $radios->attach(form_input_radio_entry($prefix . 'defines_order', '1', $defines_order == 1, do_lang_tempcode('ASCENDING')));
        $radios->attach(form_input_radio_entry($prefix . 'defines_order', '2', $defines_order == 2, do_lang_tempcode('DESCENDING')));
        $fields->attach(form_input_radio(do_lang_tempcode('DEFINES_ORDER'), do_lang_tempcode('DESCRIPTION_DEFINES_ORDER'), $prefix . 'defines_order', $radios));

        if ($first_field) {
            $hidden->attach(form_input_hidden($prefix . 'visible', '1'));
            $hidden->attach(form_input_hidden($prefix . 'required', '1'));
        } else {
            $fields->attach(form_input_tick(do_lang_tempcode('VISIBLE'), do_lang_tempcode('DESCRIPTION_VISIBLE'), $prefix . 'visible', $visible == 1));
            $fields->attach(form_input_tick(do_lang_tempcode('REQUIRED'), do_lang_tempcode('DESCRIPTION_REQUIRED'), $prefix . 'required', $required == 1));
        }
        $fields->attach(form_input_tick(do_lang_tempcode('SEARCHABLE'), do_lang_tempcode('DESCRIPTION_SEARCHABLE'), $prefix . 'searchable', $searchable == 1));
        $fields->attach(form_input_tick(do_lang_tempcode('PUT_IN_CATEGORY'), do_lang_tempcode('DESCRIPTION_PUT_IN_CATEGORY'), $prefix . 'put_in_category', $put_in_category == 1));
        $fields->attach(form_input_tick(do_lang_tempcode('PUT_IN_SEARCH'), do_lang_tempcode('DESCRIPTION_PUT_IN_SEARCH'), $prefix . 'put_in_search', $put_in_search == 1));

        return array($fields, $hidden);
    }

    /**
     * Standard crud_module edit form filler.
     *
     * @param  ID_TEXT $catalogue_name The entry being edited
     * @return array A pair: the Tempcode for the visible fields, and the Tempcode for the hidden fields
     */
    public function fill_in_edit_form($catalogue_name)
    {
        $rows = $GLOBALS['SITE_DB']->query_select('catalogues', array('*'), array('c_name' => $catalogue_name), '', 1);
        if (!array_key_exists(0, $rows)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue'));
        }
        $myrow = $rows[0];

        $title = get_translated_text($myrow['c_title']);
        $description = get_translated_text($myrow['c_description']);

        return $this->get_form_fields($catalogue_name, $title, $description, $myrow['c_display_type'], $myrow['c_is_tree'], $myrow['c_notes'], $myrow['c_submit_points'], $myrow['c_ecommerce'], $myrow['c_send_view_reports'], $myrow['c_default_review_freq']);
    }

    /**
     * Standard crud_module add actualiser.
     *
     * @return ID_TEXT The entry added
     */
    public function add_actualisation()
    {
        require_code('catalogues2');
        $name = post_param_string('name');
        $title = post_param_string('title');
        $description = post_param_string('description');
        $display_type = post_param_integer('display_type');
        $is_tree = post_param_integer('is_tree', 0);
        $this->is_tree_catalogue = ($is_tree == 1);
        $notes = post_param_string('notes', '');
        $submit_points = post_param_integer('submit_points', 0);
        $cat_tab = post_param_integer('cat_tab', 0);
        $ecommerce = post_param_integer('ecommerce', 0);
        $send_view_reports = post_param_string('send_view_reports');
        $default_review_freq = post_param_integer('default_review_freq', null);

        // What fields do we have?
        $new = array();
        foreach ($_POST as $key => $val) {
            if ((!is_string($val)) && (!is_integer($val))) {
                continue;
            }

            if (@get_magic_quotes_gpc()) {
                $val = stripslashes($val);
            }

            $matches = array();
            if (preg_match('#new_field_(\d*)_(.*)#A', $key, $matches) != 0) {
                $new[$matches[1]][$matches[2]] = $val;
            }
        }

        $num_fields = 0;
        foreach ($new as $field) {
            if ($field['name'] != '') {
                $num_fields++;
            }
        }
        if (($num_fields == 0) && (substr($name, 0, 1) != '_')) {
            warn_exit(do_lang_tempcode('NO_FIELDS'));
        }

        $metadata = actual_metadata_get_fields('catalogue', null);

        actual_add_catalogue($name, $title, $description, $display_type, $is_tree, $notes, $submit_points, $ecommerce, $send_view_reports, $default_review_freq, $metadata['add_time']);

        set_url_moniker('catalogue', $name);

        $category_id = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'id', array('c_name' => $name));

        $this->set_permissions($name);
        if (!is_null($category_id)) {
            $GLOBALS['MODULE_CMS_CATALOGUES']->cat_crud_module->set_permissions(strval($category_id));
        }

        if (addon_installed('content_reviews')) {
            content_review_set('catalogue', $name);
        }

        // Now onto the fields
        foreach ($new as $field) {
            if (!array_key_exists('default', $field)) {
                warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
            }
            if (!array_key_exists('description', $field)) {
                warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
            }
            if (!array_key_exists('name', $field)) {
                warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
            }
            if (!array_key_exists('order', $field)) {
                warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
            }
            if ((!array_key_exists('type', $field)) || ($field['type'] == '')) {
                warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
            }

            if ($field['order'] == '') {
                $field['order'] = 0;
            } else {
                $field['order'] = intval($field['order']);
            }
            $defines_order = array_key_exists('defines_order', $field) ? intval($field['defines_order']) : 0;
            $visible = array_key_exists('visible', $field) ? intval($field['visible']) : 0;
            $searchable = array_key_exists('searchable', $field) ? intval($field['searchable']) : 0;
            $required = array_key_exists('required', $field) ? intval($field['required']) : 0;
            $put_in_category = array_key_exists('put_in_category', $field) ? intval($field['put_in_category']) : 0;
            $put_in_search = array_key_exists('put_in_search', $field) ? intval($field['put_in_search']) : 0;
            $options = array_key_exists('options', $field) ? $field['options'] : '';
            if ($field['name'] != '') {
                actual_add_catalogue_field($name, $field['name'], $field['description'], $field['type'], $field['order'], $defines_order, $visible, $searchable, $field['default'], $required, $put_in_category, $put_in_search, $options);
            }
        }

        // Auto-fill feature
        $auto_fill = post_param_string('auto_fill');
        if ($auto_fill != '') {
            $categories = array();
            if (strpos($auto_fill, '|') === false) {
                $to_do = explode(',', $auto_fill);
            } else {
                $to_do = explode('|', $auto_fill);
            }

            // Prevent double submission warnings for catalogue categories; they will break the creation of the catalogue
            set_mass_import_mode(true);

            foreach ($to_do as $doing) {
                if (trim($doing) == '') {
                    continue;
                }

                $bits = explode('\\', $doing);
                $parent_id = $category_id;
                foreach ($bits as $bit) {
                    $bit = trim($bit);

                    if (array_key_exists($bit, $categories)) {
                        if (!is_null($parent_id)) {
                            $parent_id = $categories[$bit];
                        }
                    } else {
                        $_parent_id = actual_add_catalogue_category($name, $bit, '', '', $parent_id, '');
                        if (!is_null($parent_id)) {
                            $parent_id = $_parent_id;
                        }
                        require_code('permissions2');
                        if (get_value('disable_cat_cat_perms') !== '1') {
                            set_category_permissions_from_environment('catalogues_category', strval($parent_id), $this->privilege_page);
                        }
                        $categories[$bit] = $parent_id;
                    }
                }
            }

            set_mass_import_mode(false);
        }

        if (($is_tree == 0) && (substr($name, 0, 1) != '_')) {
            $this->do_next_description = paragraph(do_lang_tempcode('SUGGEST_ADD_CATEGORY_NEXT'));
        }

        return $name;
    }

    /**
     * Standard crud_module edit actualiser.
     *
     * @param  ID_TEXT $old_name The entry being edited
     */
    public function edit_actualisation($old_name)
    {
        require_code('catalogues2');

        $name = post_param_string('name', $old_name);
        $title = post_param_string('title', STRING_MAGIC_NULL);
        $description = post_param_string('description', STRING_MAGIC_NULL);
        $display_type = post_param_integer('display_type', fractional_edit() ? INTEGER_MAGIC_NULL : 0);
        $notes = post_param_string('notes', STRING_MAGIC_NULL);
        $submit_points = post_param_integer('submit_points', fractional_edit() ? INTEGER_MAGIC_NULL : 0);
        $ecommerce = post_param_integer('ecommerce', fractional_edit() ? INTEGER_MAGIC_NULL : 0);
        $send_view_reports = post_param_string('send_view_reports', STRING_MAGIC_NULL);
        if (!fractional_edit()) {
            if (post_param_integer('reset_category_permissions', 0) == 1) {
                if (php_function_allowed('set_time_limit')) {
                    @set_time_limit(0);
                }

                $start = 0;
                do {
                    $rows = $GLOBALS['SITE_DB']->query_select('catalogue_categories', array('id'), array('c_name' => $name), '', 300, $start);
                    foreach ($rows as $row) {
                        $this->set_permissions(strval($row['id']));
                    }

                    $start += 300;
                } while (array_key_exists(0, $rows));
            }
        }
        $default_review_freq = post_param_integer('default_review_freq', fractional_edit() ? INTEGER_MAGIC_NULL : null);

        $was_tree = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogues', 'c_is_tree', array('c_name' => $old_name));
        if (is_null($was_tree)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'catalogue'));
        }
        $is_tree = post_param_integer('is_tree', fractional_edit() ? INTEGER_MAGIC_NULL : 0);

        if (!fractional_edit()) {
            // What fields do we have?
            $old = array();
            $new = array();
            foreach ($_POST as $key => $val) {
                if (!is_string($val)) {
                    continue;
                }

                if (@get_magic_quotes_gpc()) {
                    $val = stripslashes($val);
                }

                $matches = array();
                if (preg_match('#new\_field\_(\d+)\_(.*)#A', $key, $matches) != 0) {
                    $new[$matches[1]][$matches[2]] = $val;
                } elseif (preg_match('#existing\_field\_(\d+)\_(.*)#A', $key, $matches) != 0) {
                    $old[$matches[1]][$matches[2]] = $val;
                }
            }
            $num_fields = 0;
            foreach ($new as $field) {
                if ($field['name'] != '') {
                    $num_fields++;
                }
            }
            foreach ($old as $field) {
                if (!((array_key_exists('delete', $field)) && ($field['delete'] == '1'))) {
                    $num_fields++;
                }
            }

            if ($num_fields == 0) {
                if (substr($old_name, 0, 1) == '_') {
                    actual_delete_catalogue($old_name);
                    return;
                }
                warn_exit(do_lang_tempcode('NO_FIELDS'));
            }
        }

        if (($is_tree == 1) && ($was_tree == 0)) {
            catalogue_to_tree($name);
        }
        if (($is_tree == 0) && ($was_tree == 1)) {
            catalogue_from_tree($name);
        }
        $this->is_tree_catalogue = ($is_tree == 1);

        $metadata = actual_metadata_get_fields('catalogue', $old_name, null, $name);

        actual_edit_catalogue($old_name, $name, $title, $description, $display_type, $notes, $submit_points, $ecommerce, $send_view_reports, $default_review_freq, $metadata['add_time']);

        if ($old_name != $name) {
            unset($_GET['redirect']);
        }

        if (addon_installed('content_reviews')) {
            content_review_set('catalogue', $name, $old_name);
        }

        $this->new_id = $name;

        if (!fractional_edit()) {
            // Now onto the fields
            //  First we must rationalise the ordering
            $o = 0;
            $orderings = array();
            foreach ($new as $current) {
                if (!array_key_exists('default', $current)) {
                    warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
                }
                if (!array_key_exists('description', $current)) {
                    warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
                }
                if (!array_key_exists('name', $current)) {
                    warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
                }
                if (!array_key_exists('order', $current)) {
                    warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
                }
                if ((!array_key_exists('type', $current)) || ($current['type'] == '')) {
                    warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
                }

                if ($current['name'] != '') {
                    if ((!array_key_exists('order', $current)) || ($current['order'] == '')) {
                        $current['order'] = strval(count($new) + count($old));
                    }
                    $orderings['new_' . strval($o)] = $current['order'];
                }
                $o++;
            }
            $o = 0;
            foreach ($old as $current) {
                if (!((array_key_exists('delete', $current)) && ($current['delete'] == '1'))) { // If not deleting
                    if ((!array_key_exists('order', $current)) || ($current['order'] == '')) {
                        $current['order'] = strval(count($new) + count($old));
                    }
                    $orderings['old_' . strval($o)] = $current['order'];
                }
                $o++;
            }
            asort($orderings);
            //  Now add/edit them
            $o = 0;
            foreach ($new as $field) {
                $p = 0;
                foreach (array_keys($orderings) as $key) {
                    if ($key == 'new_' . strval($o)) {
                        $order = $p;
                    }
                    $p++;
                }
                $defines_order = array_key_exists('defines_order', $field) ? intval($field['defines_order']) : 0;
                $visible = array_key_exists('visible', $field) ? intval($field['visible']) : 0;
                $searchable = array_key_exists('searchable', $field) ? intval($field['searchable']) : 0;
                $required = array_key_exists('required', $field) ? intval($field['required']) : 0;
                $put_in_category = array_key_exists('put_in_category', $field) ? intval($field['put_in_category']) : 0;
                $put_in_search = array_key_exists('put_in_search', $field) ? intval($field['put_in_search']) : 0;
                $options = $field['options'];
                if ($field['name'] != '') {
                    actual_add_catalogue_field($name, $field['name'], $field['description'], $field['type'], $order, $defines_order, $visible, $searchable, $field['default'], $required, $put_in_category, $put_in_search, $options);
                }
                $o++;
            }
            $o = 0;
            foreach ($old as $id => $field) {
                if ((array_key_exists('delete', $field)) && ($field['delete'] == '1')) {
                    actual_delete_catalogue_field($id);
                } else {
                    $p = 0;
                    foreach (array_keys($orderings) as $key) {
                        if ($key == 'old_' . strval($o)) {
                            $order = $p;
                        }
                        $p++;
                    }
                    $defines_order = array_key_exists('defines_order', $field) ? intval($field['defines_order']) : 0;
                    $visible = array_key_exists('visible', $field) ? intval($field['visible']) : 0;
                    $searchable = array_key_exists('searchable', $field) ? intval($field['searchable']) : 0;
                    $required = array_key_exists('required', $field) ? intval($field['required']) : 0;
                    $put_in_category = array_key_exists('put_in_category', $field) ? intval($field['put_in_category']) : 0;
                    $put_in_search = array_key_exists('put_in_search', $field) ? intval($field['put_in_search']) : 0;
                    $options = $field['options'];
                    $field_type = array_key_exists('type', $field) ? $field['type'] : null;
                    actual_edit_catalogue_field($id, $name, $field['name'], $field['description'], $order, $defines_order, $visible, $searchable, $field['default'], $required, $put_in_category, $put_in_search, $options, $field_type);
                }
                $o++;
            }
        }

        // Do this last as it causes a menu decache which can cause memory errors if we do a warn_exit (i.e. we want the warn_exit's before this)
        if (!fractional_edit()) {
            $this->set_permissions($name);
        }
    }

    /**
     * Standard crud_module delete actualiser.
     *
     * @param  ID_TEXT $id The entry being deleted
     */
    public function delete_actualisation($id)
    {
        require_code('catalogues2');

        $this->is_tree_catalogue = ($GLOBALS['SITE_DB']->query_select_value_if_there('catalogues', 'c_is_tree', array('c_name' => $id)) === 1);

        require_code('menus2');
        delete_menu_item_simple('_SEARCH:catalogues:category:catalogue_name=' . $id);
        delete_menu_item_simple('_SEARCH:catalogues:index:' . $id);

        actual_delete_catalogue($id);
    }

    /**
     * The do-next manager for after catalogue 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 $name The catalogue name we were working with (null: deleted)
     * @return Tempcode The UI
     */
    public function do_next_manager($title, $description, $name)
    {
        if (!is_null($name)) {
            $cat_count = $GLOBALS['SITE_DB']->query_select_value('catalogue_categories', 'COUNT(*)', array('c_name' => $name));
            $has_categories = ($cat_count != 0);
        } else {
            $has_categories = false;
        }

        $is_custom_fields = (!is_null($name)) && (substr($name, 0, 1) == '_');

        require_code('templates_donext');
        return do_next_manager($title, $description,
            null,
            null,
            /* TYPED-ORDERED LIST OF 'LINKS'  */
            (is_null($name) || (!$has_categories)) ? null : array('_SELF', array('type' => 'add_entry', 'catalogue_name' => $name), '_SELF'), // Add one
            null, // Edit this
            (is_null($name) || (!$has_categories)) ? null : (has_privilege(get_member(), 'edit_own_midrange_content', 'cms_catalogues') ? array('_SELF', array('type' => 'edit_entry', 'catalogue_name' => $name), '_SELF') : null), // Edit one
            null, // View this
            null, // View archive
            null, // Add to category
            (is_null($name) || $is_custom_fields) ? null : array('_SELF', array('type' => 'add_category', 'catalogue_name' => $name), '_SELF'), // Add one category
            (is_null($name) || (!$has_categories)) ? null : array('_SELF', array('type' => 'edit_category', 'catalogue_name' => $name), '_SELF'), // Edit one category
            null, // Edit this category
            null, // View this category
            /* SPECIALLY TYPED 'LINKS' */
            array(),
            array(),
            array(
                $is_custom_fields ? null : array('menu/cms/catalogues/add_one_catalogue', array('_SELF', array('type' => 'add_catalogue'), '_SELF')),
                is_null($name) ? null : array('menu/cms/catalogues/edit_this_catalogue', array('_SELF', array('type' => '_edit_catalogue', 'id' => $name), '_SELF')),
                $is_custom_fields ? null : array('menu/cms/catalogues/edit_one_catalogue', array('_SELF', array('type' => 'edit_catalogue'), '_SELF')),
                (is_null($name) || $is_custom_fields) ? null : array('menu/rich_content/catalogues/catalogues', array('catalogues', $this->is_tree_catalogue ? array('type' => 'category', 'catalogue_name' => $name) : array('type' => 'index', 'id' => $name, 'tree' => $this->is_tree_catalogue ? 1 : 0), get_module_zone('catalogues')), do_lang('VIEW_CATALOGUE'))
            ),
            do_lang('MANAGE_CATALOGUES'),
            null,
            null,
            null,
            'catalogue_entry',
            'catalogue_category'
        );
    }
}
