<?php /*

 Composr
 Copyright (c) Christopher Graham, 2004-2024

 See docs/LICENSE.md for full licensing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  Christopher Graham
 * @package    galleries
 */

require_code('crud_module');

/**
 * Module page class.
 */
class Module_cms_galleries extends Standard_crud_module
{
    protected $lang_type = 'IMAGE';
    protected $select_name_description = 'DESCRIPTION_IMAGE';
    protected $select_name = 'IMAGE';
    protected $permissions_require = 'mid';
    protected $permissions_module_require = 'galleries';
    protected $permissions_cat_name = 'cat';
    protected $user_facing = true;
    protected $seo_type = 'image';
    protected $upload = 'image';
    protected $content_type = 'image';
    protected $menu_label = 'GALLERIES';
    protected $table = 'images';
    protected $supports_mass_delete = true;

    protected $donext_type = null;

    /**
     * Find entry-points available within this module.
     *
     * @param  boolean $check_perms Whether to check permissions
     * @param  ?MEMBER $member_id The member to check permissions as (null: current user)
     * @param  boolean $support_crosslinks Whether to allow cross links to other modules (identifiable via a full-page-link rather than a screen-name)
     * @param  boolean $be_deferential Whether to avoid any entry-point (or even return null to disable the page in the Sitemap) if we know another module, or page_group, is going to link to that entry-point. Note that "!" and "browse" entry points are automatically merged with container page nodes (likely called by page-groupings) as appropriate.
     * @return ?array A map of entry points (screen-name=>language-code/string or screen-name=>[language-code/string, icon-theme-image]) (null: disabled)
     */
    public function get_entry_points(bool $check_perms = true, ?int $member_id = null, bool $support_crosslinks = true, bool $be_deferential = false) : ?array
    {
        if (!addon_installed('galleries')) {
            return null;
        }

        if ($member_id === null) {
            $member_id = get_member();
        }

        $ret = [
            'browse' => ['MANAGE_GALLERIES', 'menu/rich_content/galleries'],
        ];

        if (has_privilege($member_id, 'submit_midrange_content', 'cms_galleries')) {
            $ret += [
                'add' => ['ADD_IMAGE', 'menu/cms/galleries/add_one_image'],
            ];
        }

        if (has_privilege($member_id, 'edit_midrange_content', 'cms_galleries')) {
            $ret += [
                'edit' => ['EDIT_IMAGE', 'menu/cms/galleries/edit_one_image'],
            ];
        }

        if (has_privilege($member_id, 'submit_midrange_content', 'cms_galleries')) {
            $ret += [
                'add_other' => ['ADD_VIDEO', (get_option('allow_audio_videos') == '2') ? 'menu/cms/galleries/add_one_multimedia' : 'menu/cms/galleries/add_one_video'],
            ];
        }

        if (has_privilege($member_id, 'edit_midrange_content', 'cms_galleries')) {
            $ret += [
                'edit_other' => ['EDIT_VIDEO', (get_option('allow_audio_videos') == '2') ? 'menu/cms/galleries/edit_one_multimedia' : 'menu/cms/galleries/edit_one_video'],
            ];
        }

        if (has_privilege($member_id, 'mass_import', 'cms_galleries')) {
            $ret['import'] = ['GALLERY_IMPORT', 'admin/import'];
        }

        $this->cat_crud_module = class_exists('Mx_cms_galleries_cat') ? new Mx_cms_galleries_cat() : new Module_cms_galleries_cat();
        $this->alt_crud_module = class_exists('Mx_cms_galleries_alt') ? new Mx_cms_galleries_alt() : new Module_cms_galleries_alt();

        $ret += parent::get_entry_points();

        if ($support_crosslinks) {
            require_code('fields');
            $ret += manage_custom_fields_entry_points('image') + manage_custom_fields_entry_points('video') + manage_custom_fields_entry_points('gallery');
        }

        if (has_privilege($member_id, 'mass_import')) {
            $ret += [
                'predefined_content' => ['PREDEFINED_CONTENT', 'admin/import'],
            ];
        }

        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() : array
    {
        require_lang('galleries');
        return [
            'view_private_content' => 0,
            'mass_import' => 0,
            'have_personal_category' => [0, 'HAVE_PERSONAL_GALLERIES'],
            'submit_cat_midrange_content' => [0, 'ADD_GALLERY'],
            'edit_own_cat_midrange_content' => [0, 'EDIT_OWN_GALLERY'],
            'edit_cat_midrange_content' => [0, 'EDIT_GALLERY'],
            'delete_own_cat_midrange_content' => [0, 'DELETE_OWN_GALLERY'],
            'delete_cat_midrange_content' => [0, 'DELETE_GALLERY'],
            'may_download_gallery' => 1,
            'submit_midrange_content' => [1, 'ADD_MEDIA'],
            'bypass_validation_midrange_content' => [1, 'BYPASS_VALIDATION_MEDIA'],
            'edit_own_midrange_content' => [1, 'EDIT_OWN_MEDIA'],
            'edit_midrange_content' => [1, 'EDIT_MEDIA'],
            'delete_own_midrange_content' => [1, 'DELETE_OWN_MEDIA'],
            'delete_midrange_content' => [1, 'DELETE_MEDIA'],
        ];
    }

    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(bool $top_level = true, ?string $type = null) : ?object
    {
        $error_msg = new Tempcode();
        if (!addon_installed__messaged('galleries', $error_msg)) {
            return $error_msg;
        }

        $this->cat_crud_module = class_exists('Mx_cms_galleries_cat') ? new Mx_cms_galleries_cat() : new Module_cms_galleries_cat();
        $this->alt_crud_module = class_exists('Mx_cms_galleries_alt') ? new Mx_cms_galleries_alt() : new Module_cms_galleries_alt();
        $GLOBALS['MODULE_CMS_GALLERIES'] = $this;

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

        require_lang('galleries');

        inform_non_canonical_parameter('cat');
        inform_non_canonical_parameter('validated');
        inform_non_canonical_parameter('parent_id');

        if ($type == 'import') {
            inform_non_canonical_parameter('member_id');
        }

        if ($type == '_import') {
            breadcrumb_set_parents([['_SELF:_SELF:import', do_lang_tempcode('GALLERY_IMPORT')]]);
            breadcrumb_set_self(do_lang_tempcode('UPLOAD'));
        }

        if ($type == '__import') {
            $cat = get_param_string('cat');
            breadcrumb_set_parents([['_SELF:_SELF:import', do_lang_tempcode('GALLERY_IMPORT')], ['_SELF:_SELF:_import:name=' . $cat, do_lang_tempcode('DETAILS')]]);
            breadcrumb_set_self(do_lang_tempcode('DONE'));
        }

        if (($type == 'orphaned')) {
            $cat = post_param_string('ss_cat');
            breadcrumb_set_parents([['_SELF:_SELF:import', do_lang_tempcode('GALLERY_IMPORT')], ['_SELF:_SELF:_import:name=' . $cat, do_lang_tempcode('DETAILS')]]);
            breadcrumb_set_self(do_lang_tempcode('DONE'));
        }

        if ($type == 'add') {
            if (has_privilege(get_member(), 'mass_import', 'cms_galleries')) {
                $cat = get_param_string('cat', null);
                $mass_url = build_url(['page' => '_SELF', 'type' => 'import', 'cat' => $cat], '_SELF');
                attach_message(do_lang_tempcode('CAN_MASS_UPLOAD', escape_html($mass_url->evaluate())), 'inform', true);
            }
        }

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

        if ($type == '_import') {
            $cat = get_param_string('name', 'root');
            $this->title = get_screen_title('_GALLERY_IMPORT', true, [escape_html($cat)]);
        }

        if ($type == '__import') {
            $cat = get_param_string('cat');
            $this->title = get_screen_title('_GALLERY_IMPORT', true, [escape_html($cat)]);
        }

        if ($type == 'predefined_content') {
        }

        if ($type == '_predefined_content') {
            breadcrumb_set_parents([['_SELF:_SELF:browse', do_lang_tempcode('MANAGE_GALLERIES')], ['_SELF:_SELF:predefined_content', do_lang_tempcode('PREDEFINED_CONTENT')]]);
            breadcrumb_set_self(do_lang_tempcode('DONE'));
        }

        if ($type == 'predefined_content' || $type == '_predefined_content') {
            $this->title = get_screen_title('PREDEFINED_CONTENT');
        }

        set_helper_panel_tutorial('tut_galleries');

        return parent::pre_run($top_level);
    }

    /**
     * Standard crud_module run_start.
     *
     * @param  ID_TEXT $type The type of module execution
     * @return Tempcode The output of the run
     */
    public function run_start(string $type) : object
    {
        require_code('galleries');
        require_code('galleries2');
        require_code('uploads');
        require_css('galleries');
        require_javascript('galleries');
        require_lang('dearchive');
        require_code('urls2');
        require_code('images');

        $this->alt_crud_module->add_text = new Tempcode();

        $cat = get_param_string('cat', '');

        if ($cat != '') {
            if (strpos($type, '_other') !== false) { // Video
                $remaining = $this->alt_crud_module->check_videos_allowed($cat, true);
                if ($remaining !== null) {
                    $this->alt_crud_module->add_text->attach(paragraph(do_lang_tempcode('X_ENTRIES_REMAINING', escape_html(integer_format($remaining, 0)))));
                }
            } elseif (strpos($type, '_category') === false) { // Image
                $remaining = $this->check_images_allowed($cat, true);
                if ($remaining !== null) {
                    $this->add_text = paragraph(do_lang_tempcode('X_ENTRIES_REMAINING', escape_html(integer_format($remaining, 0))));
                }
            }
        }
        if (get_option('allow_audio_videos') == '1') {
            $this->alt_crud_module->add_text->attach(paragraph(do_lang_tempcode('ALSO_AUDIO')));
        }

        if ($type == 'add_category') {
            $this->cat_crud_module->js_function_calls[] = 'moduleCmsGalleriesRunStartAddCategory';
        }

        // Decide what to do
        if ($type == 'browse') {
            return $this->browse();
        }
        if ($type == 'import') {
            return $this->import();
        }
        if ($type == '_import') {
            return $this->_import();
        }
        if ($type == '__import') {
            return $this->__import();
        }
        if ($type == '_orphaned') {
            return $this->_orphaned();
        }
        if ($type == 'predefined_content') {
            return $this->predefined_content();
        }
        if ($type == '_predefined_content') {
            return $this->_predefined_content();
        }

        return new Tempcode();
    }

    /**
     * The do-next manager for before content management.
     *
     * @return Tempcode The UI
     */
    public function browse() : object
    {
        $allow_images = ($GLOBALS['SITE_DB']->query_select_value('galleries', 'COUNT(*)', ['accept_images' => 1]) > 0);
        $allow_videos = ($GLOBALS['SITE_DB']->query_select_value('galleries', 'COUNT(*)', ['accept_videos' => 1]) > 0);

        require_code('templates_donext');
        require_code('fields');
        return do_next_manager(
            get_screen_title('MANAGE_GALLERIES'),
            comcode_lang_string('DOC_GALLERIES'),
            array_merge([
                has_privilege(get_member(), 'submit_cat_midrange_content', 'cms_galleries') ? ['admin/add_one_category', ['_SELF', ['type' => 'add_category'], '_SELF'], do_lang('ADD_GALLERY')] : null,
                has_privilege(get_member(), 'edit_own_cat_midrange_content', 'cms_galleries') ? ['admin/edit_one_category', ['_SELF', ['type' => 'edit_category'], '_SELF'], do_lang('EDIT_GALLERY')] : null,
                has_privilege(get_member(), 'mass_import', 'cms_galleries') ? ['admin/import', ['_SELF', ['type' => 'import'], '_SELF'], do_lang('GALLERY_IMPORT'), 'DOC_GALLERY_IMPORT'] : null,
                (!$allow_images) ? null : (has_privilege(get_member(), 'submit_midrange_content', 'cms_galleries') ? ['menu/cms/galleries/add_one_image', ['_SELF', ['type' => 'add'], '_SELF'], do_lang('ADD_IMAGE')] : null),
                (!$allow_images) ? null : (has_privilege(get_member(), 'edit_own_midrange_content', 'cms_galleries') ? ['menu/cms/galleries/edit_one_image', ['_SELF', ['type' => 'edit'], '_SELF'], do_lang('EDIT_IMAGE')] : null),
                (!$allow_videos) ? null : (has_privilege(get_member(), 'submit_midrange_content', 'cms_galleries') ? [(get_option('allow_audio_videos') == '2') ? 'menu/cms/galleries/add_one_multimedia' : 'menu/cms/galleries/add_one_video', ['_SELF', ['type' => 'add_other'], '_SELF'], do_lang('ADD_VIDEO')] : null),
                (!$allow_videos) ? null : (has_privilege(get_member(), 'edit_own_midrange_content', 'cms_galleries') ? [(get_option('allow_audio_videos') == '2') ? 'menu/cms/galleries/edit_one_multimedia' : 'menu/cms/galleries/edit_one_video', ['_SELF', ['type' => 'edit_other'], '_SELF'], do_lang('EDIT_VIDEO')] : null),
                has_privilege(get_member(), 'mass_import') ? ['admin/install', ['_SELF', ['type' => 'predefined_content'], '_SELF'], do_lang('PREDEFINED_CONTENT')] : null,
            ], manage_custom_fields_donext_link('image'), manage_custom_fields_donext_link('video'), manage_custom_fields_donext_link('gallery')),
            do_lang('MANAGE_GALLERIES')
        );
    }

    /**
     * The UI to choose a gallery to import into.
     *
     * @return Tempcode The UI
     */
    public function import() : object
    {
        check_privilege('mass_import');

        $condition = 'only_member_galleries_of_id';
        $member_id = get_param_integer('member_id', null);
        if ($member_id === null) {
            $condition = null;
        }

        $cat = get_param_string('cat', '');

        $post_url = build_url(['page' => '_SELF', 'type' => '_import'], '_SELF', [], false, true);

        $fields = new Tempcode();

        $fields->attach(form_input_tree_list(do_lang_tempcode('GALLERY'), '', 'name', null, 'choose_gallery', ['must_accept_something' => true, 'purity' => false, 'addable_filter' => true, 'filter' => $condition, 'member_id' => $member_id], true, $cat));

        $submit_name = do_lang_tempcode('GALLERY_IMPORT');

        return do_template('FORM_SCREEN', [
            '_GUID' => '5213edb75c8d534c121b587c555a3b9a',
            'TITLE' => $this->title,
            'SKIP_WEBSTANDARDS' => true,
            'GET' => true,
            'HIDDEN' => '',
            'TEXT' => '',
            'FIELDS' => $fields,
            'URL' => $post_url,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
        ]);
    }

    /**
     * The UI to import into a gallery.
     *
     * @return Tempcode The UI
     */
    public function _import() : object
    {
        $cat = get_param_string('name', 'root');
        check_privilege('mass_import'/*Not currently scoped to categories, ['galleries', $cat]*/);

        if (substr($cat, 0, 7) != 'member_') {
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('galleries', 'name', ['name' => $cat]);
            if ($test === null) {
                warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'gallery'));
            }
        }

        require_lang('trackbacks');

        // To choose to batch import from multiple attached files
        $post_url = build_url(['page' => '_SELF', 'type' => '__import', 'cat' => $cat, 'uploading' => 1, 'redirect' => protect_url_parameter(get_param_string('redirect', null, INPUT_FILTER_URL_INTERNAL))], '_SELF');
        $fields = new Tempcode();
        $supported = get_option('valid_images') . ',' . get_allowed_video_file_types();
        $fields->attach(form_input_upload_multi(do_lang_tempcode('UPLOAD'), do_lang_tempcode('DESCRIPTION_ARCHIVE_MEDIA', escape_html(str_replace(',', ', ', $supported))), 'file', true, null, null, true, str_replace(' ', '', $supported)));
        $fields->attach(form_input_line(do_lang_tempcode('TITLE'), (get_option('gallery_media_title_required') == '2') ? do_lang_tempcode('DESCRIPTION_GALLERY_IMPORT_TITLE_REQUIRED') : do_lang_tempcode('DESCRIPTION_GALLERY_IMPORT_TITLE'), 'set_title', '', false/*When required, title can be left blank to auto-generate from file names or EXIF*/));
        $hidden = new Tempcode();
        handle_max_file_size($hidden);

        if ($this->has_at_least_one_watermark($cat)) {
            $fields->attach(form_input_tick(do_lang_tempcode('WATERMARK'), do_lang_tempcode('DESCRIPTION_WATERMARK'), 'watermark', true));
        }

        // Feedback
        require_code('feedback2');
        $fields->attach(feedback_fields($this->content_type, true, true, true, false, ''));
        // Privacy
        if (addon_installed('content_privacy')) {
            require_code('content_privacy2');
            $fields->attach(get_privacy_form_fields(null));
        }
        // Max upload limit
        require_code('files2');
        $max = floatval(get_max_file_size()) / floatval(1024 * 1024);
        if ($max < 30.0) {
            $text = get_maximum_upload_message($max);
        } else {
            $text = new Tempcode();
        }
        // Show form
        $hidden->attach(form_input_hidden('test', '1'));
        $form = do_template('FORM', [
            '_GUID' => '05de2bc134ed0b1ab703ac8bfbfabce4',
            'TABINDEX' => strval(get_form_field_tabindex()),
            'FIELDS' => $fields,
            'SUBMIT_ICON' => 'admin/import',
            'SUBMIT_NAME' => do_lang_tempcode('BATCH_IMPORT_ARCHIVE_CONTENTS'),
            'URL' => $post_url,
            'TEXT' => $text,
            'HIDDEN' => $hidden,
        ]);

        // Orphaned upload form
        // To choose to batch import what already exists in gallery directory, but is orphaned
        $orphaned_content = new Tempcode();
        if ((has_actual_page_access(get_member(), 'admin_cleanup')) && ($GLOBALS['SITE_DB']->query_select_value('images', 'COUNT(*)') + $GLOBALS['SITE_DB']->query_select_value('videos', 'COUNT(*)') < 4000)) {
            require_code('files');
            $there = [];
            $_dir = opendir(get_custom_file_base() . '/uploads/galleries/');
            while (false !== ($file = readdir($_dir))) {
                if ((get_file_extension($file) != 'vtt') && (!should_ignore_file($file, IGNORE_ACCESS_CONTROLLERS)) && (!is_dir(get_custom_file_base() . '/uploads/galleries/' . $file)) && ((is_image($file, IMAGE_CRITERIA_WEBSAFE, has_privilege(get_member(), 'comcode_dangerous'))) || (is_video($file, has_privilege(get_member(), 'comcode_dangerous'))))) {
                    $there[$file] = filemtime(get_custom_file_base() . '/uploads/galleries/' . $file);
                }
            }
            closedir($_dir);
            $_dir = @opendir(get_custom_file_base() . '/uploads/galleries/' . filter_naughty($cat));
            if ($_dir !== false) {
                while (false !== ($file = readdir($_dir))) {
                    if ((!should_ignore_file($file, IGNORE_ACCESS_CONTROLLERS)) && (!is_dir(get_custom_file_base() . '/uploads/galleries/' . $cat . '/' . $file)) && ((is_image($file, IMAGE_CRITERIA_WEBSAFE, has_privilege(get_member(), 'comcode_dangerous'))) || (is_video($file, has_privilege(get_member(), 'comcode_dangerous'))))) {
                        $there[$cat . '/' . $file] = filemtime(get_custom_file_base() . '/uploads/galleries/' . $cat . '/' . $file);
                    }
                }
                closedir($_dir);
            }
            if (!empty($there)) {
                cms_mb_asort($there, SORT_FLAG_CASE | SORT_NATURAL);
                $test1 = collapse_1d_complexity('url', $GLOBALS['SITE_DB']->query_select('images', ['url']));
                $test2 = collapse_1d_complexity('url', $GLOBALS['SITE_DB']->query_select('videos', ['url']));
                arsort($there);
                $temp_keys = array_keys($there);
                $last_time = $there[$temp_keys[0]];
                foreach ($there as $file => $time) {
                    if (!is_string($file)) {
                        $file = strval($file);
                    }

                    if ((!in_array(cms_rawurlrecode('uploads/galleries/' . str_replace('%2F', '/', rawurlencode($file))), $test1)) && (!in_array(cms_rawurlrecode('uploads/galleries/' . str_replace('%2F', '/', rawurlencode($file))), $test2))) {
                        $orphaned_content->attach(form_input_list_entry($file, ($time >= $last_time - 60 * 60 * 3) || (strpos($file, '/') !== false), $file));
                    }
                }
            }
            $add_url = build_url(['page' => '_SELF', 'type' => '_orphaned'], '_SELF');
            if (!$orphaned_content->is_empty()) {
                $fields_2 = new Tempcode();
                $hidden_2 = new Tempcode();

                $hidden_2->attach(form_input_hidden('ss_cat', $cat));
                $fields_2->attach(form_input_multi_list(do_lang_tempcode('ENTRIES'), '', 'ss_files', $orphaned_content));
                $fields_2->attach(form_input_line(do_lang_tempcode('TITLE'), do_lang_tempcode('DESCRIPTION_GALLERY_IMPORT_TITLE'), 'ss_set_title', '', get_option('gallery_media_title_required') == '2'));
                if ($this->has_at_least_one_watermark($cat)) {
                    $fields_2->attach(form_input_various_ticks([[do_lang_tempcode('WATERMARK'), 'ss_watermark', true, '']], '', null, do_lang_tempcode('OPTIONS')));
                }
                $radios = new Tempcode();
                $radios->attach(form_input_radio_entry('orphan_action', 'add', true, do_lang_tempcode('ADD_GALLERY_SELECTION')));
                $radios->attach(form_input_radio_entry('orphan_action', 'delete', false, do_lang_tempcode('DELETE_SELECTION')));
                $fields_2->attach(form_input_radio(do_lang_tempcode('ACTION'), '', 'type', $radios, true));
                // Feedback
                require_code('feedback2');
                $fields_2->attach(feedback_fields($this->content_type, true, true, true, false, '', null, false, true, true, 'ss_'));
                // Privacy
                if (addon_installed('content_privacy')) {
                    require_code('content_privacy2');
                    $fields_2->attach(get_privacy_form_fields(null, null, true, 'ss_'));
                }

                require_code('syndication');
                $fields_2->attach(get_syndication_option_fields('image'));

                $form2 = do_template('FORM', [
                    '_GUID' => '79c9fd4f29197460f08443bf2ffdf8b2',
                    'SECONDARY_FORM' => true,
                    'TABINDEX' => strval(get_form_field_tabindex()),
                    'FIELDS' => $fields_2,
                    'SUBMIT_ICON' => 'admin/import',
                    'SUBMIT_NAME' => do_lang_tempcode('PROCEED'),
                    'URL' => $add_url,
                    'TEXT' => '',
                    'HIDDEN' => $hidden_2,
                ]);
            } else {
                $form2 = new Tempcode();
            }
        } else {
            $form2 = new Tempcode();
        }

        return do_template('GALLERY_IMPORT_SCREEN', ['_GUID' => '607c819ff751268294e5e590a0d41533', 'TITLE' => $this->title, 'FORM2' => $form2, 'FORM' => $form]);
    }

    /**
     * The actualiser to import multiple uploaded files into a gallery.
     *
     * @return Tempcode The UI
     */
    public function __import() : object
    {
        $cat = get_param_string('cat');

        require_code('files2');

        check_privilege('mass_import'/*Not currently scoped to categories, ['galleries', $cat]*/);

        post_param_string('test'); // To pick up on max file size exceeded errors
        $set_title = post_param_string('set_title', '');
        $allow_rating = post_param_integer('allow_rating', 0);
        $allow_comments_reviews = post_param_integer('allow_reviews', post_param_integer('allow_comments', 0));
        $allow_trackbacks = post_param_integer('allow_trackbacks', 0);
        $notes = post_param_string('notes', '');
        $watermark = (post_param_integer('watermark', 0) == 1);
        $member_id = get_member();

        if (addon_installed('content_privacy')) {
            require_code('content_privacy2');
            list($privacy_level, $additional_access) = read_privacy_fields();
        } else {
            $privacy_level = '';
            $additional_access = [];
        }

        make_member_gallery_if_needed($cat);

        is_plupload(true);

        require_code('uploads');

        $files = [];

        // Do initial checking of imported files
        foreach ($_FILES as $attach_name => $__file) {
            $filename = $__file['name'];
            if ($filename == '') {
                continue; // Not filled in this one
            }

            if (!check_extension($filename, false, null, true)) {
                continue;
            }

            $tmp_name = $__file['tmp_name'];

            if ((!is_plupload()) && (!is_uploaded_file($tmp_name))) {
                $upload_error_message = get_upload_error_message($__file);
                attach_message($upload_error_message, 'warn');
                continue;
            }

            $target_path = get_temporary_upload_path($attach_name);

            if ((is_image($filename, IMAGE_CRITERIA_WEBSAFE, has_privilege(get_member(), 'comcode_dangerous'))) || (is_video($filename, has_privilege(get_member(), 'comcode_dangerous')))) {
                $files[$target_path] = $filename;
            } else {
                attach_message(do_lang_tempcode('INVALID_FILE_TYPE_VERY_GENERAL', escape_html(get_file_extension($filename))), 'warn');
            }
        }

        if (empty($files)) {
            warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN_UPLOAD'));
        }

        $title = get_translated_text($GLOBALS['SITE_DB']->query_select_value('galleries', 'fullname', ['name' => $cat]));
        log_it('GALLERY_IMPORT', $cat, $title);

        require_code('tasks');
        $ret_message = call_user_func_array__long_task(do_lang('GALLERY_IMPORT'), null, 'import_gallery_media', [$files, $member_id, $cat, $set_title, $allow_rating, $allow_comments_reviews, $allow_trackbacks, $watermark, $notes, $privacy_level, $additional_access]);

        $url = build_url(['page' => '_SELF', 'type' => '_import', 'name' => $cat], '_SELF');
        return redirect_screen($this->title, $url, $ret_message);
    }

    /**
     * The actualiser to add/delete orphaned files in a gallery.
     *
     * @return Tempcode The UI
     */
    public function _orphaned() : object
    {
        $cat = post_param_string('ss_cat');
        $action = post_param_string('orphan_action');

        $member_id = get_member();

        if (!has_actual_page_access($member_id, 'admin_cleanup')) {
            access_denied('I_ERROR');
        }

        check_privilege('mass_import'/*Not currently scoped to categories, ['galleries', $cat]*/);

        // Deleting orphaned files should happen immediately; no task necessary
        if ($action === 'delete') {
            if (array_key_exists('ss_files', $_POST)) {
                foreach ($_POST['ss_files'] as $file) {
                    if (!is_string($file)) {
                        continue;
                    }

                    $path = get_custom_file_base() . '/uploads/galleries/' . filter_naughty($file);
                    @unlink($path) or intelligent_write_error($path);
                    sync_file($path);
                }
            }

            // Show it worked / Refresh
            $url = build_url(['page' => '_SELF', 'type' => '_import', 'name' => $cat], '_SELF');
            return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
        }

        $set_title = post_param_string('ss_set_title', '');
        $allow_rating = post_param_integer('ss_allow_rating', 0);
        $allow_comments_reviews = post_param_integer('ss_allow_reviews', post_param_integer('ss_allow_comments', 0));
        $allow_trackbacks = post_param_integer('ss_allow_trackbacks', 0);
        $watermark = (post_param_integer('ss_watermark', 0) == 1);
        $notes = post_param_string('ss_notes', '');

        if (addon_installed('content_privacy')) {
            require_code('content_privacy2');
            list($privacy_level, $additional_access) = read_privacy_fields('ss_');
        } else {
            $privacy_level = '';
            $additional_access = [];
        }

        require_code('exif');
        require_code('urls2');

        make_member_gallery_if_needed($cat);

        $files = [];

        // Initialize temp directory
        $temp_path = get_custom_file_base() . '/temp';
        if (!file_exists($temp_path)) {
            make_missing_directory($temp_path);
        }

        // Do initial checking of orphaned files and move them to temp directory to prevent double-import
        if (array_key_exists('ss_files', $_POST)) {
            foreach ($_POST['ss_files'] as $filename) {
                $current_url = rawurldecode('uploads/galleries/' . str_replace('%2F', '/', rawurlencode($filename)));
                if ((is_image($filename, IMAGE_CRITERIA_WEBSAFE, has_privilege(get_member(), 'comcode_dangerous'))) || (is_video($filename, has_privilege(get_member(), 'comcode_dangerous')))) {
                    list($target_path) = find_unique_path('temp', filter_naughty($filename), true);
                    if (rename($current_url, $target_path) === false) {
                        intelligent_write_error($target_path);
                    }
                    fix_permissions($target_path);
                    sync_file_move($current_url, $target_path);

                    $files[$target_path] = $filename;
                } else {
                    attach_message(do_lang_tempcode('INVALID_FILE_TYPE_VERY_GENERAL', escape_html(get_file_extension($filename))), 'warn');
                }
            }
        }

        if (empty($files)) {
            warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN_UPLOAD'));
        }

        $title = get_translated_text($GLOBALS['SITE_DB']->query_select_value('galleries', 'fullname', ['name' => $cat]));
        log_it('GALLERY_IMPORT', $cat, $title);

        require_code('tasks');
        $ret_message = call_user_func_array__long_task(do_lang('GALLERY_IMPORT'), null, 'import_orphaned_gallery_media', [$files, $member_id, $cat, $set_title, $allow_rating, $allow_comments_reviews, $allow_trackbacks, $watermark, $notes, $privacy_level, $additional_access]);

        $url = build_url(['page' => '_SELF', 'type' => '_import', 'name' => $cat], '_SELF');
        return redirect_screen($this->title, $url, $ret_message);
    }

    /**
     * Kills the software if someone is trying to add an image to a gallery that does not support images.
     *
     * @param  ID_TEXT $cat The gallery
     * @param  boolean $tolerate_non_exist Whether to tolerate the non-existence of the gallery
     * @return ?integer Number allowed remaining (null: no limit)
     */
    public function check_images_allowed(string $cat, bool $tolerate_non_exist = false) : ?int
    {
        // Check this cat allows images
        $gallery = $GLOBALS['SITE_DB']->query_select('galleries', ['accept_videos', 'accept_images', 'is_member_synched'], ['name' => $cat], '', 1);
        $owner = get_member_id_from_gallery_name($cat);
        if (!array_key_exists(0, $gallery)) {
            if ($tolerate_non_exist) {
                $limit = null;
                if ((!has_privilege(get_member(), 'no_personal_gallery_limit')) && ($owner == get_member())) {
                    $limit = has_privilege(get_member(), 'high_personal_gallery_limit') ? intval(get_option('max_personal_gallery_images_high')) : intval(get_option('max_personal_gallery_images_low'));
                }
                return $limit;
            }
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'gallery'));
        }
        if (($gallery[0]['accept_images'] == 0) || ($gallery[0]['is_member_synched'] == 1)) {
            warn_exit(do_lang_tempcode(($gallery[0]['accept_images'] == 1) ? 'ERROR_NOT_ACCEPT_CONTAINER' : 'ERROR_NOT_ACCEPT_IMAGES', escape_html($cat)), false, false, 500, $this->title);
        }

        if ((!has_privilege(get_member(), 'no_personal_gallery_limit')) && ($owner == get_member())) {
            $limit = has_privilege(get_member(), 'high_personal_gallery_limit') ? intval(get_option('max_personal_gallery_images_high')) : intval(get_option('max_personal_gallery_images_low'));
            $count_already = $GLOBALS['SITE_DB']->query_select_value('images', 'COUNT(*)', ['cat' => $cat]);
            if ($count_already >= $limit) {
                warn_exit(do_lang_tempcode('TOO_MANY_GALLERY_IMAGES'));
            }

            return $limit - $count_already;
        }

        return null;
    }

    /**
     * Standard crud_module tree-list function.
     *
     * @return array A triple: The tree field (Tempcode), Search URL, Archive URL
     */
    public function create_selection_list_ajax_tree() : array
    {
        if ($GLOBALS['SITE_DB']->query_select_value('images', 'COUNT(*)') == 0) {
            inform_exit(do_lang_tempcode('NO_ENTRIES', 'image'));
        }

        $search_url = build_url(['page' => 'search', 'id' => 'images'], get_module_zone('search'));
        $archive_url = build_url(['page' => 'galleries'], get_module_zone('galleries'));

        $only_owned = has_privilege(get_member(), 'edit_midrange_content', 'cms_galleries') ? null : get_member();
        $tree = form_input_tree_list(do_lang_tempcode('IMAGE'), do_lang_tempcode('DESCRIPTION_IMAGE'), 'id', null, 'choose_image', ['filter' => 'only_conventional_galleries', 'only_owned' => $only_owned, 'editable_filter' => true], true, null, false, null, $this->supports_mass_delete);
        return [$tree, $search_url, $archive_url];
    }

    /**
     * Get Tempcode for an adding form.
     *
     * @return mixed Either Tempcode; or a tuple of: (fields, hidden-fields[, delete-fields][, edit-text][, whether all delete fields are specified][, posting form text, more fields][, parsed WYSIWYG editable text])
     */
    public function get_form_fields_for_add()
    {
        return $this->get_form_fields();
    }

    /**
     * Get Tempcode for an image adding/editing form.
     *
     * @param  ?AUTO_LINK $id The ID of the image (null: new)
     * @param  SHORT_TEXT $title The title
     * @param  ID_TEXT $cat The gallery
     * @param  LONG_TEXT $description The image description
     * @param  URLPATH $url URL to the image
     * @param  BINARY $validated Image validation status
     * @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  LONG_TEXT $notes Notes for the image
     * @param  array $regions The regions (empty: not region-limited)
     * @return array A pair: The input fields, Hidden fields
     */
    public function get_form_fields(?int $id = null, string $title = '', string $cat = '', string $description = '', string $url = '', int $validated = 1, ?int $allow_rating = null, ?int $allow_comments = null, ?int $allow_trackbacks = null, string $notes = '', array $regions = []) : array
    {
        list($allow_rating, $allow_comments, $allow_trackbacks) = $this->choose_feedback_fields_statistically($allow_rating, $allow_comments, $allow_trackbacks);

        $num_galleries = $GLOBALS['SITE_DB']->query_select_value('galleries', 'COUNT(*)', ['accept_images' => 1]);

        $adding = ($id === null);

        if ($adding) {
            $cat = get_param_string('cat', '');
            if ($cat != '') {
                if ($GLOBALS['SITE_DB']->query_select_value_if_there('galleries', 'is_member_synched', ['name' => $cat]) === 1) {
                    $cat = 'member_' . strval(get_member()) . '_' . $cat;
                }
            }
        }

        $fields = new Tempcode();
        $hidden = new Tempcode();

        if (strpos($cat, '?') !== false) {
            $cat = str_replace('?', strval(get_member()), $cat);
        }
        $filters = ['must_accept_images' => true, 'addable_filter' => true];
        if (substr($cat, 0, 9) != 'download_') {
            $filters['filter'] = 'only_conventional_galleries';
        }

        $fields->attach(form_input_line(do_lang_tempcode('TITLE'), do_lang_tempcode('DESCRIPTION_TITLE'), 'title', $title, get_option('gallery_media_title_required') != '0'));

        $gallery_title = '';
        if ($cat != '') {
            $gallery_title = get_potential_gallery_title($cat);
            if ($gallery_title === null) {
                $gallery_title = '';
            }
        } else {
            if ($num_galleries == 1 && $GLOBALS['SITE_DB']->query_select_value('galleries', 'is_member_synched', ['accept_images' => 1]) == 0) {
                $cat = 'root'; // Only option!
            }
        }
        if (((get_value('no_confirm_url_spec_cats') !== '1') && (($id !== null) || (substr($cat, 0, 9) != 'download_'))) || ($cat == '')) {
            $root_cat = get_value('root_cat__images', null, true);
            if (($root_cat !== null) && (preg_match('#^[' . URL_CONTENT_REGEXP . ']+$#', $root_cat) == 0)) {
                $filters['filter'] = $root_cat;
                $root_cat = '';
            }
            if (empty($root_cat) && $GLOBALS['SITE_DB']->query_select_value('galleries', 'accept_images', ['name' => 'root']) == 0) {
                $root_cat = 'root'; // Don't show 'root' itself
            }
            if ($num_galleries == 1 && $GLOBALS['SITE_DB']->query_select_value('galleries', 'is_member_synched', ['accept_images' => 1]) == 0) {
                $cat = $GLOBALS['SITE_DB']->query_select_value('galleries', 'name', ['accept_images' => 1]);
                $hidden->attach(form_input_hidden('cat', $cat));
            } else {
                $fields->attach(form_input_tree_list(do_lang_tempcode('GALLERY'), do_lang_tempcode('DESCRIPTION_GALLERY'), 'cat', $root_cat, 'choose_gallery', $filters, true, $cat, false, null, false, $gallery_title));
            }
        } else {
            $hidden->attach(form_input_hidden('cat', $cat));
        }

        $fields->attach(form_input_upload_multi_source(do_lang_tempcode('IMAGE'), '', $hidden, 'image', null, true, $url, false, null, IMAGE_CRITERIA_WEBSAFE));

        $fields->attach(form_input_text_comcode(do_lang_tempcode('DESCRIPTION'), do_lang_tempcode('DESCRIPTION_DESCRIPTION_ACCESSIBILITY'), 'description', $description, false));
        $_validated = get_param_integer('validated', 0);
        if ($validated == 0) {
            if (($_validated == 1) && (addon_installed('validation'))) {
                $validated = 1;
                attach_message(do_lang_tempcode('WILL_BE_VALIDATED_WHEN_SAVING'));
            }
        } elseif (($validated == 1) && ($_validated == 1) && ($id !== null)) {
            $action_log = build_url(['page' => 'admin_actionlog', 'type' => 'list', 'to_type' => 'VALIDATE_IMAGE', 'param_a' => strval($id)]);
            attach_message(do_lang_tempcode('ALREADY_VALIDATED', escape_html($action_log->evaluate())), 'notice');
        }
        $validated_field = new Tempcode();
        if (has_some_cat_privilege(get_member(), 'bypass_validation_' . $this->permissions_require . 'range_content', null, $this->permissions_module_require)) {
            $validated_field = form_input_tick(do_lang_tempcode('VALIDATED'), do_lang_tempcode($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member()) ? 'DESCRIPTION_VALIDATED_SIMPLE' : 'DESCRIPTION_VALIDATED', 'image'), 'validated', $validated == 1);
        }
        $fields->attach($validated_field);

        $do_watermark = ($this->has_at_least_one_watermark($cat));
        $do_rep_image = ((get_option('gallery_rep_image') == '1') && (($cat == '') || (has_edit_permission('cat_mid', get_member(), get_member_id_from_gallery_name($cat), 'cms_galleries', ['galleries', $cat]))));
        if (($do_watermark) || ($do_rep_image)) {
            $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', ['_GUID' => '971e7db21c3b9d2c8cfbd6a910711514', 'SECTION_HIDDEN' => true, 'TITLE' => do_lang_tempcode('ADVANCED')]));
            if ($do_watermark) {
                $fields->attach(form_input_tick(do_lang_tempcode('WATERMARK'), do_lang_tempcode('DESCRIPTION_WATERMARK'), 'watermark', true)); // Only applies to new uploads, so can't be watermarked twice
            }
            if ($do_rep_image) {
                $fields->attach(form_input_tick(do_lang_tempcode('REPRESENTATIVE_IMAGE'), do_lang_tempcode('_DESCRIPTION_REPRESENTATIVE_IMAGE'), 'rep_image', false));
            }
        }

        if (get_option('filter_regions') == '1') {
            require_code('locations');
            $fields->attach(form_input_regions($regions));
        }

        require_code('syndication');
        $fields->attach(get_syndication_option_fields('image', !$adding));

        // Metadata
        require_code('content2');
        $seo_fields = seo_get_fields($this->seo_type, ($id === null) ? null : strval($id), false);
        if (get_option('gallery_feedback_fields') == '1') {
            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);
        } else {
            $hidden->attach(form_input_hidden('allow_rating', strval($allow_rating)));
            $hidden->attach(form_input_hidden('allow_comments', strval($allow_comments)));
            $hidden->attach(form_input_hidden('allow_trackbacks', strval($allow_trackbacks)));
            $feedback_fields = new Tempcode();
        }
        $fields->attach(metadata_get_fields('image', ($id === null) ? null : strval($id), false, [], ($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_privacy')) {
            require_code('content_privacy2');
            if ($id === null) {
                $fields->attach(get_privacy_form_fields('image'));
            } else {
                $fields->attach(get_privacy_form_fields('image', strval($id)));
            }
        }

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

        return [$fields, $hidden];
    }

    /**
     * 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(string $id) : array
    {
        $rows = $GLOBALS['SITE_DB']->query_select('images', ['submitter', 'add_date'], ['id' => intval($id)], '', 1);
        if (!array_key_exists(0, $rows)) {
            return [null, null];
        }
        return [$rows[0]['submitter'], $rows[0]['add_date']];
    }

    /**
     * Standard crud_module category getter.
     *
     * @param  ID_TEXT $id The entry for which the category is sought
     * @return string The category
     */
    public function get_cat(string $id) : string
    {
        $temp = $GLOBALS['SITE_DB']->query_select_value_if_there('images', 'cat', ['id' => intval($id)]);
        if ($temp === null) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'image'));
        }
        return $temp;
    }

    /**
     * Standard crud_module edit form filler.
     *
     * @param  ID_TEXT $_id The entry being edited
     * @return mixed Either Tempcode; or a tuple of: (fields, hidden-fields[, delete-fields][, edit-text][, whether all delete fields are specified][, posting form text, more fields][, parsed WYSIWYG editable text])
     */
    public function fill_in_edit_form(string $_id)
    {
        $id = intval($_id);

        $rows = $GLOBALS['SITE_DB']->query_select('images', ['*'], ['id' => $id], '', 1);
        if (!array_key_exists(0, $rows)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'image'));
        }
        $myrow = $rows[0];
        $description = get_translated_text($myrow['the_description']);
        $cat = $myrow['cat'];
        $validated = $myrow['validated'];

        $delete_fields = null;
        if (get_option('cleanup_files') == '1') {
            if (has_delete_permission('mid', get_member(), $myrow['submitter'], 'cms_galleries', ['galleries', $cat])) {
                if (has_actual_page_access(get_member(), 'admin_cleanup')) {
                    $radios = form_input_radio_entry('delete', '0', true, do_lang_tempcode('LEAVE'));
                    $radios->attach(form_input_radio_entry('delete', '1', false, do_lang_tempcode('DELETE_PARTIAL')));
                    $radios->attach(form_input_radio_entry('delete', '2', false, do_lang_tempcode('DELETE_FULL')));
                    $delete_fields = form_input_radio(do_lang_tempcode('DELETE_STATUS'), do_lang_tempcode('DESCRIPTION_DELETE_STATUS'), 'delete', $radios);
                } else {
                    $delete_fields = form_input_tick(do_lang_tempcode('DELETE'), do_lang_tempcode('DESCRIPTION_DELETE'), 'delete', false);
                }
            } else {
                $delete_fields = new Tempcode();
            }
        }

        $regions = collapse_1d_complexity('region', $GLOBALS['SITE_DB']->query_select('content_regions', ['region'], ['content_type' => 'image', 'content_id' => strval($id)]));

        $ret = $this->get_form_fields($id, get_translated_text($myrow['title']), $cat, $description, $myrow['url'], $validated, $myrow['allow_rating'], $myrow['allow_comments'], $myrow['allow_trackbacks'], $myrow['notes'], $regions);

        $ret[2] = $delete_fields;
        $ret[3] = '';
        $ret[4] = $delete_fields !== null;
        return $ret;
    }

    /**
     * Standard crud_module add actualiser.
     *
     * @return array A pair: The entry added, description about usage
     */
    public function add_actualisation() : array
    {
        $cat = post_param_string('cat', 'root');
        $description = post_param_string('description', '');

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

        // Images cleanup pipeline
        $maximum_dimension = intval(get_option('maximum_image_size'));
        $watermark = (post_param_integer('watermark', 0) == 1);
        $watermarks = $watermark ? find_gallery_watermarks($cat) : null;
        set_images_cleanup_pipeline_settings(IMG_RECOMPRESS_LOSSLESS, $maximum_dimension, $watermarks, get_value('keep_gallery_gps', '0') == '0');

        list(
            $url,
            $filename,
            $title
        ) = image_get_defaults__post();

        if (can_submit_to_gallery($cat) === false) {
            access_denied('SUBMIT_HERE');
        }
        make_member_gallery_if_needed($cat);
        $this->check_images_allowed($cat);

        reset_images_cleanup_pipeline_settings();

        require_code('upload_syndication');
        $url = handle_upload_syndication('image__upload', $title, $description, $url, $filename, false);

        $this->donext_type = $cat;

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

        $regions = isset($_POST['regions']) ? $_POST['regions'] : [];

        $id = add_image($title, $cat, $description, $url, $validated, $allow_rating, $allow_comments, $allow_trackbacks, $notes, $metadata['submitter'], $metadata['add_time'], $metadata['edit_time'], $metadata['views'], null, '', '', $regions);

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

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

        if (($validated == 1) || (!addon_installed('validation'))) {
            require_code('users2');
            if ((has_actual_page_access(get_modal_user(), 'galleries')) && (has_category_access(get_modal_user(), 'galleries', $cat))) {
                $privacy_ok = true;
                if (addon_installed('content_privacy')) {
                    require_code('content_privacy');
                    $privacy_ok = has_privacy_access('image', strval($id), $GLOBALS['FORUM_DRIVER']->get_guest_id());
                }
                if ($privacy_ok) {
                    require_code('syndication');
                    syndicate_content('image', strval($id), [['galleries:ACTIVITY_ADD_IMAGE', get_member(), ($title == '') ? $filename : $title]]);
                }
            }
        }

        if ((has_edit_permission('cat_mid', get_member(), get_member_id_from_gallery_name($cat), 'cms_galleries', ['galleries', $cat])) && (post_param_integer('rep_image', 0) == 1)) {
            $GLOBALS['SITE_DB']->query_update('galleries', ['rep_image' => $url], ['name' => $cat], '', 1);
        }

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

        return [strval($id), null];
    }

    /**
     * Standard crud_module edit actualiser.
     *
     * @param  ID_TEXT $_id The entry being edited
     * @return ?Tempcode Description about usage (null: none)
     */
    public function edit_actualisation(string $_id) : ?object
    {
        $id = intval($_id);
        $cat = post_param_string('cat', fractional_edit() ? STRING_MAGIC_NULL : 'root');

        if ($cat != STRING_MAGIC_NULL) {
            if (can_submit_to_gallery($cat) === false) {
                access_denied('SUBMIT_HERE');
            }

            make_member_gallery_if_needed($cat);
            $this->check_images_allowed($cat);
        }

        // Images cleanup pipeline
        $maximum_dimension = intval(get_option('maximum_image_size'));
        $watermark = (post_param_integer('watermark', 0) == 1);
        $watermarks = $watermark ? find_gallery_watermarks($cat) : null;
        set_images_cleanup_pipeline_settings(IMG_RECOMPRESS_LOSSLESS, $maximum_dimension, $watermarks, get_value('keep_gallery_gps', '0') == '0');

        list(
            $url,
            $filename,
            $title
        ) = image_get_defaults__post(true);

        $description = post_param_string('description', STRING_MAGIC_NULL);

        $validated = post_param_integer('validated', fractional_edit() ? INTEGER_MAGIC_NULL : 0);

        if (!fractional_edit()) {
            require_code('upload_syndication');
            $url = handle_upload_syndication('image__upload', $title, $description, $url, $filename, false);
        }

        reset_images_cleanup_pipeline_settings();

        $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);
        $notes = post_param_string('notes', fractional_edit() ? STRING_MAGIC_NULL : '');
        $allow_trackbacks = post_param_integer('allow_trackbacks', fractional_edit() ? INTEGER_MAGIC_NULL : 0);

        $this->donext_type = $cat;

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

        if (($validated == 1) || (!addon_installed('validation'))) {
            require_code('users2');
            if ((has_actual_page_access(get_modal_user(), 'galleries')) && (has_category_access(get_modal_user(), 'galleries', $cat))) {
                $privacy_ok = true;
                if (addon_installed('content_privacy')) {
                    require_code('content_privacy');
                    $privacy_ok = has_privacy_access('image', strval($id), $GLOBALS['FORUM_DRIVER']->get_guest_id());
                }
                if ($privacy_ok) {
                    $just_validated = (addon_installed('validation')) && ($GLOBALS['SITE_DB']->query_select_value('images', 'validated', ['id' => $id]) == 0);
                    $submitter = $GLOBALS['SITE_DB']->query_select_value('images', 'submitter', ['id' => $id]);

                    $activities = [];
                    if ($just_validated) {
                        if ($submitter != get_member()) {
                            $activities[] = ['galleries:ACTIVITY_VALIDATE_IMAGE', get_member()];
                        }
                        $activities[] = ['galleries:ACTIVITY_ADD_IMAGE', $submitter];
                    }

                    require_code('syndication');
                    syndicate_content('image', strval($id), $activities);
                }
            }
        }

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

        $regions = isset($_POST['regions']) ? $_POST['regions'] : [];

        edit_image($id, $title, $cat, $description, $url, $validated, $allow_rating, $allow_comments, $allow_trackbacks, $notes, post_param_string('meta_keywords', ''), post_param_string('meta_description', ''), $metadata['edit_time'], $metadata['add_time'], $metadata['views'], $metadata['submitter'], $regions, true);

        if ((!fractional_edit()) && (has_edit_permission('cat_mid', get_member(), get_member_id_from_gallery_name($cat), 'cms_galleries', ['galleries', $cat])) && (post_param_integer('rep_image', 0) == 1)) {
            $GLOBALS['SITE_DB']->query_update('galleries', ['rep_image' => $url], ['name' => $cat], '', 1);
        }

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

        return null;
    }

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

        $delete_status = post_param_string('delete', '1');

        $cat = post_param_string('cat', null);
        if ($cat !== null) {
            $this->donext_type = $cat;
        }

        delete_image($id, ($delete_status == '2') || (get_option('cleanup_files') == '1'));

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

        require_code('syndication');
        unsyndicate_content('image', strval($id));
    }

    /**
     * See if a gallery has any watermarks to use, or all galleries.
     *
     * @param  ?ID_TEXT $cat The gallery (null: all the current user has access to)
     * @return boolean Whether watermarks are available
     */
    public function has_at_least_one_watermark(?string $cat = null) : bool
    {
        $where = '';
        if ($cat !== null) {
            $where = db_string_equal_to('name', $cat) . ' AND ';
        }
        $where .= '(' . db_string_not_equal_to('watermark_top_left', '');
        $where .= ' OR ' . db_string_not_equal_to('watermark_top_left', '');
        $where .= ' OR ' . db_string_not_equal_to('watermark_top_right', '');
        $where .= ' OR ' . db_string_not_equal_to('watermark_bottom_left', '');
        $where .= ' OR ' . db_string_not_equal_to('watermark_bottom_right', '') . ')';
        $gals = $GLOBALS['SITE_DB']->query('SELECT name FROM ' . get_table_prefix() . 'galleries WHERE ' . $where);
        foreach ($gals as $guy) {
            $cat = $guy['name'];
            if (has_category_access(get_member(), 'galleries', $cat)) {
                return true;
            }
        }

        return false;
    }

    /**
     * The do-next manager for after content management.
     *
     * @param  ?Tempcode $title The title (output of get_screen_title) (null: don't do full page)
     * @param  Tempcode $description Some description to show, saying what happened
     * @param  ?ID_TEXT $id The ID of whatever we are working with (null: deleted)
     * @return Tempcode The UI
     */
    public function do_next_manager(?object $title, object $description, ?string $id = null) : object
    {
        return $this->cat_crud_module->_do_next_manager($title, $description, $this->donext_type, ($id === null) ? null : intval($id));
    }

    /**
     * UI for install/uninstall of predefined content.
     *
     * @return Tempcode The UI
     */
    public function predefined_content() : object
    {
        require_code('content2');
        return predefined_content_changes_ui('galleries', $this->title, build_url(['page' => '_SELF', 'type' => '_predefined_content'], '_SELF'));
    }

    /**
     * Actualise install/uninstall of predefined content.
     *
     * @return Tempcode The UI
     */
    public function _predefined_content() : object
    {
        require_code('content2');
        return predefined_content_changes_actualiser('galleries', $this->title);
    }
}

/**
 * Module page class.
 */
class Module_cms_galleries_alt extends Standard_crud_module
{
    protected $lang_type = 'VIDEO';
    protected $select_name = 'NAME';
    protected $permissions_require = 'mid';
    protected $permissions_module_require = 'galleries';
    protected $permissions_cat_name = 'cat';
    protected $user_facing = true;
    protected $seo_type = 'video';
    protected $upload = 'file';
    protected $javascript = '';
    protected $content_type = 'video';
    protected $menu_label = 'GALLERIES';
    protected $table = 'videos';
    protected $supports_mass_delete = true;
    protected $is_chained_with_parent_browse = true;

    protected $donext_type = null;

    /**
     * Kills the software if someone is trying to add a video to a gallery that does not support videos.
     *
     * @param  ID_TEXT $cat The gallery
     * @param  boolean $tolerate_non_exist Whether to tolerate the non-existence of the gallery
     * @return ?integer Number allowed remaining (null: no limit)
     */
    public function check_videos_allowed(string $cat, bool $tolerate_non_exist = false) : ?int
    {
        // Check this cat allows images
        $gallery = $GLOBALS['SITE_DB']->query_select('galleries', ['accept_videos', 'accept_images', 'is_member_synched'], ['name' => $cat], '', 1);
        $owner = get_member_id_from_gallery_name($cat);
        if (!array_key_exists(0, $gallery)) {
            if ($tolerate_non_exist) {
                $limit = null;
                if ((!has_privilege(get_member(), 'no_personal_gallery_limit')) && ($owner == get_member())) {
                    $limit = has_privilege(get_member(), 'high_personal_gallery_limit') ? intval(get_option('max_personal_gallery_videos_high')) : intval(get_option('max_personal_gallery_videos_low'));
                }
                return $limit;
            }
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'gallery'));
        }
        if (($gallery[0]['accept_videos'] == 0) || ($gallery[0]['is_member_synched'] == 1)) {
            warn_exit(do_lang_tempcode(($gallery[0]['accept_videos'] == 1) ? 'ERROR_NOT_ACCEPT_CONTAINER' : 'ERROR_NOT_ACCEPT_VIDEOS', escape_html($cat)), false, false, 500, $this->title);
        }

        if ((!has_privilege(get_member(), 'no_personal_gallery_limit')) && ($owner == get_member())) {
            $limit = has_privilege(get_member(), 'high_personal_gallery_limit') ? intval(get_option('max_personal_gallery_videos_high')) : intval(get_option('max_personal_gallery_videos_low'));
            $count_already = $GLOBALS['SITE_DB']->query_select_value('videos', 'COUNT(*)', ['cat' => $cat]);
            if ($count_already >= $limit) {
                warn_exit(do_lang_tempcode('TOO_MANY_GALLERY_VIDEOS'));
            }

            return $limit - $count_already;
        }

        return null;
    }

    /**
     * Standard crud_module tree-list function.
     *
     * @return array A triple: The tree field (Tempcode), Search URL, Archive URL
     */
    public function create_selection_list_ajax_tree() : array
    {
        if ($GLOBALS['SITE_DB']->query_select_value('videos', 'COUNT(*)') == 0) {
            inform_exit(do_lang_tempcode('NO_ENTRIES', 'video'));
        }

        $search_url = build_url(['page' => 'search', 'id' => 'videos'], get_module_zone('search'));
        $archive_url = build_url(['page' => 'galleries'], get_module_zone('galleries'));

        $only_owned = has_privilege(get_member(), 'edit_midrange_content', 'cms_galleries') ? null : get_member();
        $tree = form_input_tree_list(do_lang_tempcode('VIDEO'), '', 'id', null, 'choose_video', ['only_owned' => $only_owned, 'editable_filter' => true], true, null, false, null, $this->supports_mass_delete);
        return [$tree, $search_url, $archive_url];
    }

    /**
     * Get Tempcode for an adding form.
     *
     * @return mixed Either Tempcode; or a tuple of: (fields, hidden-fields[, delete-fields][, edit-text][, whether all delete fields are specified][, posting form text, more fields][, parsed WYSIWYG editable text])
     */
    public function get_form_fields_for_add()
    {
        return $this->get_form_fields();
    }

    /**
     * Get Tempcode for a video adding/editing form.
     *
     * @param  ?AUTO_LINK $id The ID of the video (null: new)
     * @param  SHORT_TEXT $title The title
     * @param  ID_TEXT $cat The gallery
     * @param  LONG_TEXT $description The video description
     * @param  URLPATH $url The URL to the video file (blank: not yet added)
     * @param  URLPATH $thumb_url The URL to the thumbnail
     * @param  BINARY $validated Video validation status
     * @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  LONG_TEXT $notes Notes for the video
     * @param  ?integer $video_length The length of the video (null: not yet added, so not yet known)
     * @param  ?integer $video_width The width of the video (null: not yet added, so not yet known)
     * @param  ?integer $video_height The height of the video (null: not yet added, so not yet known)
     * @param  URLPATH $closed_captions_url The URL to the closed captions for this video
     * @param  array $regions The regions (empty: not region-limited)
     * @return array A pair: The input fields, Hidden fields
     */
    public function get_form_fields(?int $id = null, string $title = '', string $cat = '', string $description = '', string $url = '', string $thumb_url = '', int $validated = 1, ?int $allow_rating = null, ?int $allow_comments = null, ?int $allow_trackbacks = null, string $notes = '', ?int $video_length = null, ?int $video_width = null, ?int $video_height = null, string $closed_captions_url = '', array $regions = []) : array
    {
        list($allow_rating, $allow_comments, $allow_trackbacks) = $this->choose_feedback_fields_statistically($allow_rating, $allow_comments, $allow_trackbacks);

        $no_thumb_needed = (get_option('ffmpeg_path') != '') || (class_exists('ffmpeg_movie'));

        $num_galleries = $GLOBALS['SITE_DB']->query_select_value('galleries', 'COUNT(*)', ['accept_videos' => 1]);

        if ($cat == '') {
            $cat = get_param_string('cat', '');
            if ($cat != '') {
                if ($GLOBALS['SITE_DB']->query_select_value_if_there('galleries', 'is_member_synched', ['name' => $cat]) === 1) {
                    $cat = 'member_' . strval(get_member()) . '_' . $cat;
                }
            }
        }

        $fields = new Tempcode();
        $hidden = new Tempcode();
        handle_max_file_size($hidden);
        if (strpos($cat, '?') !== false) {
            $cat = str_replace('?', strval(get_member()), $cat);
        }
        $filters = ['filter' => 'only_conventional_galleries', 'must_accept_videos' => true, 'addable_filter' => true];

        $fields->attach(form_input_line(do_lang_tempcode('TITLE'), do_lang_tempcode('DESCRIPTION_TITLE'), 'title', $title, get_option('gallery_media_title_required') != '0'));

        $gallery_title = '';
        if ($cat != '') {
            $gallery_title = get_potential_gallery_title($cat);
            if ($gallery_title === null) {
                $gallery_title = '';
            }
        } else {
            if ($num_galleries == 1 && $GLOBALS['SITE_DB']->query_select_value('galleries', 'is_member_synched', ['accept_videos' => 1]) == 0) {
                $cat = 'root'; // Only option!
            }
        }
        if ((get_value('no_confirm_url_spec_cats') !== '1') || ($cat == '')) {
            $root_cat = get_value('root_cat__videos', null, true);
            if (($root_cat !== null) && (preg_match('#^[' . URL_CONTENT_REGEXP . ']+$#', $root_cat) == 0)) {
                $filters['filter'] = $root_cat;
                $root_cat = '';
            }
            if (empty($root_cat) && $GLOBALS['SITE_DB']->query_select_value('galleries', 'accept_videos', ['name' => 'root']) == 0) {
                $root_cat = 'root'; // Don't show 'root' itself
            }
            if ($num_galleries == 1 && $GLOBALS['SITE_DB']->query_select_value('galleries', 'is_member_synched', ['accept_videos' => 1]) == 0) {
                $cat = $GLOBALS['SITE_DB']->query_select_value('galleries', 'name', ['accept_videos' => 1]);
                $hidden->attach(form_input_hidden('cat', $cat));
            } else {
                $fields->attach(form_input_tree_list(do_lang_tempcode('GALLERY'), do_lang_tempcode('DESCRIPTION_GALLERY'), 'cat', $root_cat, 'choose_gallery', $filters, true, $cat, false, null, false, $gallery_title));
            }
        } else {
            $hidden->attach(form_input_hidden('cat', $cat));
        }

        $supported = get_allowed_video_file_types();

        $fields->attach(form_input_upload_multi_source(do_lang_tempcode('VIDEO'), '', $hidden, 'video', null, true, $url, false, $supported, null));

        $_validated = get_param_integer('validated', 0);
        if ($validated == 0) {
            if (($_validated == 1) && (addon_installed('validation'))) {
                $validated = 1;
                attach_message(do_lang_tempcode('WILL_BE_VALIDATED_WHEN_SAVING'));
            }
        } elseif (($validated == 1) && ($_validated == 1) && ($id !== null)) {
            $action_log = build_url(['page' => 'admin_actionlog', 'type' => 'list', 'to_type' => 'VALIDATE_VIDEO', 'param_a' => strval($id)]);
            attach_message(do_lang_tempcode('ALREADY_VALIDATED', escape_html($action_log->evaluate())), 'notice');
        }
        $validated_field = new Tempcode();
        if (has_some_cat_privilege(get_member(), 'bypass_validation_' . $this->permissions_require . 'range_content', null, $this->permissions_module_require)) {
            if (addon_installed('validation')) {
                $validated_field = form_input_tick(do_lang_tempcode('VALIDATED'), do_lang_tempcode($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member()) ? 'DESCRIPTION_VALIDATED_SIMPLE' : 'DESCRIPTION_VALIDATED', 'video'), 'validated', $validated == 1);
            }
        }

        $description_field = form_input_text_comcode(do_lang_tempcode('DESCRIPTION'), do_lang_tempcode('DESCRIPTION_DESCRIPTION_ACCESSIBILITY'), 'description', $description, false);
        if ($no_thumb_needed) {
            $fields->attach($description_field);
            $fields->attach($validated_field);
            $temp = do_template('FORM_SCREEN_FIELD_SPACER', ['_GUID' => '9843d9412a6659d06b4667c2fa0f0e45', 'TITLE' => do_lang_tempcode('ADVANCED'), 'SECTION_HIDDEN' => true]);
            $fields->attach($temp);
        }

        $thumb_width = get_option('thumb_width');
        $thumbnail_required = false;//(!$no_thumb_needed) && (get_option('allow_audio_videos') == '0') && (find_theme_image('video_thumb', true) == ''); Vimeo won't require one for example
        $fields->attach(form_input_upload_multi_source(do_lang_tempcode('THUMBNAIL'), do_lang_tempcode('DESCRIPTION_THUMBNAIL', escape_html($thumb_width)), $hidden, 'video__thumb', null, $thumbnail_required, $thumb_url, false, null, IMAGE_CRITERIA_WEBSAFE));

        if (!$no_thumb_needed) {
            $fields->attach($description_field);
        }
        $fields->attach(form_input_integer(do_lang_tempcode('VIDEO_LENGTH'), do_lang_tempcode('DESCRIPTION_VIDEO_LENGTH'), 'video_length', $video_length, false));
        $fields->attach(form_input_dimensions(do_lang_tempcode('DIMENSIONS'), do_lang_tempcode('DESCRIPTION_VIDEO_DIMENSIONS'), 'video_width', 'video_height', $video_width, $video_height, false));
        if (!$no_thumb_needed) {
            $fields->attach($validated_field);
        }

        // Advanced
        $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', ['_GUID' => 'c09d066b272c40ef91f058c40b5c27d0', 'SECTION_HIDDEN' => true, 'TITLE' => do_lang_tempcode('ADVANCED')]));
        if (get_option('filter_regions') == '1') {
            require_code('locations');
            $fields->attach(form_input_regions($regions));
        }
        $fields->attach(form_input_upload_multi_source(do_lang_tempcode('CLOSED_CAPTIONS'), do_lang_tempcode('DESCRIPTION_CLOSED_CAPTIONS'), $hidden, 'closed_captions_url', null, false, $closed_captions_url, false, 'vtt', null));

        require_code('syndication');
        $fields->attach(get_syndication_option_fields('video', $id !== null));

        // Metadata
        require_code('content2');
        $seo_fields = seo_get_fields($this->seo_type, ($id === null) ? null : strval($id), false);
        if (get_option('gallery_feedback_fields') == '1') {
            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);
        } else {
            $hidden->attach(form_input_hidden('allow_rating', strval($allow_rating)));
            $hidden->attach(form_input_hidden('allow_comments', strval($allow_comments)));
            $hidden->attach(form_input_hidden('allow_trackbacks', strval($allow_trackbacks)));
            $feedback_fields = new Tempcode();
        }
        $fields->attach(metadata_get_fields('video', ($id === null) ? null : strval($id), false, [], ($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_privacy')) {
            require_code('content_privacy2');
            if ($id === null) {
                $fields->attach(get_privacy_form_fields('video'));
            } else {
                $fields->attach(get_privacy_form_fields('video', strval($id)));
            }
        }

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

        return [$fields, $hidden];
    }

    /**
     * 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(string $id) : array
    {
        $rows = $GLOBALS['SITE_DB']->query_select('videos', ['submitter', 'add_date'], ['id' => intval($id)], '', 1);
        if (!array_key_exists(0, $rows)) {
            return [null, null];
        }
        return [$rows[0]['submitter'], $rows[0]['add_date']];
    }

    /**
     * Standard crud_module category getter.
     *
     * @param  ID_TEXT $id The entry for which the category is sought
     * @return string The category
     */
    public function get_cat(string $id) : string
    {
        $temp = $GLOBALS['SITE_DB']->query_select_value_if_there('videos', 'cat', ['id' => intval($id)]);
        if ($temp === null) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'video'));
        }
        return $temp;
    }

    /**
     * Standard crud_module edit form filler.
     *
     * @param  ID_TEXT $_id The entry being edited
     * @return mixed Either Tempcode; or a tuple of: (fields, hidden-fields[, delete-fields][, edit-text][, whether all delete fields are specified][, posting form text, more fields][, parsed WYSIWYG editable text])
     */
    public function fill_in_edit_form(string $_id)
    {
        $id = intval($_id);

        $rows = $GLOBALS['SITE_DB']->query_select('videos', ['*'], ['id' => $id], '', 1);
        if (!array_key_exists(0, $rows)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'video'));
        }
        $myrow = $rows[0];
        $description = get_translated_text($myrow['the_description']);
        $url = $myrow['url'];
        $cat = $myrow['cat'];
        $validated = $myrow['validated'];

        $delete_fields = null;
        if (get_option('cleanup_files') == '1') {
            if (has_delete_permission('mid', get_member(), $myrow['submitter'], 'cms_galleries', ['galleries', $cat])) {
                if (has_actual_page_access(get_member(), 'admin_cleanup')) {
                    $radios = form_input_radio_entry('delete', '0', true, do_lang_tempcode('LEAVE'));
                    $radios->attach(form_input_radio_entry('delete', '1', false, do_lang_tempcode('DELETE_PARTIAL')));
                    $radios->attach(form_input_radio_entry('delete', '2', false, do_lang_tempcode('DELETE_FULL')));
                    $delete_fields = form_input_radio(do_lang_tempcode('DELETE_STATUS'), do_lang_tempcode('DESCRIPTION_DELETE_STATUS'), 'delete', $radios);
                } else {
                    $delete_fields = form_input_tick(do_lang_tempcode('DELETE'), do_lang_tempcode('DESCRIPTION_DELETE'), 'delete', false);
                }
            } else {
                $delete_fields = new Tempcode();
            }
        }

        $regions = collapse_1d_complexity('region', $GLOBALS['SITE_DB']->query_select('content_regions', ['region'], ['content_type' => 'video', 'content_id' => strval($id)]));

        $ret = $this->get_form_fields($id, get_translated_text($myrow['title']), $cat, $description, $url, $myrow['thumb_url'], $validated, $myrow['allow_rating'], $myrow['allow_comments'], $myrow['allow_trackbacks'], $myrow['notes'], $myrow['video_length'], $myrow['video_width'], $myrow['video_height'], $myrow['closed_captions_url'], $regions);

        $ret[2] = $delete_fields;
        $ret[3] = '';
        $ret[4] = $delete_fields !== null;
        return $ret;
    }

    /**
     * Standard crud_module add actualiser.
     *
     * @return array A pair: The entry added, description about usage
     */
    public function add_actualisation() : array
    {
        list(
            $url,
            $thumb_url,
            $filename,
            $video_width,
            $video_height,
            $video_length,
            $closed_captions_url,
            $title,
            $cat,
        ) = video_get_defaults__post();

        if (can_submit_to_gallery($cat) === false) {
            access_denied('SUBMIT_HERE');
        }
        make_member_gallery_if_needed($cat);
        $this->check_videos_allowed($cat);

        $description = post_param_string('description', '');

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

        $this->donext_type = $cat;

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

        $regions = isset($_POST['regions']) ? $_POST['regions'] : [];

        $id = add_video($title, $cat, $description, $url, $thumb_url, $validated, $allow_rating, $allow_comments, $allow_trackbacks, $notes, $video_length, $video_width, $video_height, $closed_captions_url, $metadata['submitter'], $metadata['add_time'], $metadata['edit_time'], $metadata['views'], null, '', '', $regions);

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

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

        if (($validated == 1) || (!addon_installed('validation'))) {
            require_code('users2');
            if ((has_actual_page_access(get_modal_user(), 'galleries')) && (has_category_access(get_modal_user(), 'galleries', $cat))) {
                $privacy_ok = true;
                if (addon_installed('content_privacy')) {
                    require_code('content_privacy');
                    $privacy_ok = has_privacy_access('video', strval($id), $GLOBALS['FORUM_DRIVER']->get_guest_id());
                }
                if ($privacy_ok) {
                    require_code('syndication');
                    syndicate_content('video', strval($id), [['galleries:ACTIVITY_ADD_VIDEO', get_member(), ($title == '') ? $filename : $title]]);
                }
            }
        }

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

        return [strval($id), null];
    }

    /**
     * Standard crud_module edit actualiser.
     *
     * @param  ID_TEXT $_id The entry being edited
     * @return ?Tempcode Description about usage (null: none)
     */
    public function edit_actualisation(string $_id) : ?object
    {
        $id = intval($_id);

        list(
            $url,
            $thumb_url,
            $filename,
            $video_width,
            $video_height,
            $video_length,
            $closed_captions_url,
            $title,
            $cat,
        ) = video_get_defaults__post(true);

        if ($cat != STRING_MAGIC_NULL) {
            if (can_submit_to_gallery($cat) === false) {
                access_denied('SUBMIT_HERE');
            }
            make_member_gallery_if_needed($cat);
            $this->check_videos_allowed($cat);
        }

        $description = post_param_string('description', STRING_MAGIC_NULL);

        $validated = post_param_integer('validated', fractional_edit() ? INTEGER_MAGIC_NULL : 0);

        $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);
        $notes = post_param_string('notes', fractional_edit() ? STRING_MAGIC_NULL : '');
        $allow_trackbacks = post_param_integer('allow_trackbacks', fractional_edit() ? INTEGER_MAGIC_NULL : 0);

        $this->donext_type = $cat;

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

        if (($validated == 1) || (!addon_installed('validation'))) {
            require_code('users2');
            if ((has_actual_page_access(get_modal_user(), 'galleries')) && (has_category_access(get_modal_user(), 'galleries', $cat))) {
                $privacy_ok = true;
                if (addon_installed('content_privacy')) {
                    require_code('content_privacy');
                    $privacy_ok = has_privacy_access('video', strval($id), $GLOBALS['FORUM_DRIVER']->get_guest_id());
                }
                if ($privacy_ok) {
                    $just_validated = (addon_installed('validation')) && ($GLOBALS['SITE_DB']->query_select_value('videos', 'validated', ['id' => $id]) == 0);
                    $submitter = $GLOBALS['SITE_DB']->query_select_value('videos', 'submitter', ['id' => $id]);

                    $activities = [];
                    if ($just_validated) {
                        if ($submitter != get_member()) {
                            $activities[] = ['galleries:ACTIVITY_VALIDATE_VIDEO', get_member()];
                        }
                        $activities[] = ['galleries:ACTIVITY_ADD_VIDEO', $submitter];
                    }

                    require_code('syndication');
                    syndicate_content('video', strval($id), $activities);
                }
            }
        }

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

        $regions = isset($_POST['regions']) ? $_POST['regions'] : [];

        edit_video($id, $title, $cat, $description, $url, $thumb_url, $validated, $allow_rating, $allow_comments, $allow_trackbacks, $notes, $video_length, $video_width, $video_height, post_param_string('meta_keywords', ''), post_param_string('meta_description', ''), $closed_captions_url, $metadata['edit_time'], $metadata['add_time'], $metadata['views'], $metadata['submitter'], $regions, true);

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

        return null;
    }

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

        $delete_status = post_param_string('delete', '1');

        $cat = post_param_string('cat', null);
        if ($cat !== null) {
            $this->donext_type = $cat;
        }

        delete_video($id, ($delete_status == '2') || (get_option('cleanup_files') == '1'));

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

        require_code('syndication');
        unsyndicate_content('video', strval($id));
    }

    /**
     * The do-next manager for after content management.
     *
     * @param  ?Tempcode $title The title (output of get_screen_title) (null: don't do full page)
     * @param  Tempcode $description Some description to show, saying what happened
     * @param  ?ID_TEXT $id The ID of whatever we are working with (null: deleted)
     * @return Tempcode The UI
     */
    public function do_next_manager(?object $title, object $description, ?string $id = null) : object
    {
        return $GLOBALS['MODULE_CMS_GALLERIES']->cat_crud_module->_do_next_manager($title, $description, $this->donext_type, ($id === null) ? null : intval($id), true);
    }
}

/**
 * Module page class.
 */
class Module_cms_galleries_cat extends Standard_crud_module
{
    protected $lang_type = 'GALLERY';
    protected $select_name = 'NAME';
    protected $permissions_require = 'cat_mid';
    protected $permission_module = 'galleries';
    protected $seo_type = 'gallery';
    protected $upload = 'image';
    protected $non_integer_id = true;
    protected $content_type = 'gallery';
    protected $menu_label = 'GALLERIES';
    protected $table = 'galleries';
    protected $is_chained_with_parent_browse = true;

    public $js_function_calls = ['moduleCmsGalleriesCat'];

    /**
     * Standard crud_module tree-list function.
     *
     * @return array A triple: The tree field (Tempcode), Search URL, Archive URL
     */
    public function create_selection_list_ajax_tree() : array
    {
        $purity = true;
        $_member_id = null;

        $search_url = build_url(['page' => 'search', 'id' => 'galleries'], get_module_zone('search'));
        $archive_url = build_url(['page' => 'galleries'], get_module_zone('galleries'));

        $tree = form_input_tree_list(do_lang_tempcode('GALLERY'), '', 'id', null, 'choose_gallery', ['purity' => $purity, 'member_id' => $_member_id, 'editable_filter' => true], true);
        return [$tree, $search_url, $archive_url];
    }

    /**
     * Get Tempcode for an adding form.
     *
     * @return mixed Either Tempcode; or a tuple of: (fields, hidden-fields[, delete-fields][, edit-text][, whether all delete fields are specified][, posting form text, more fields][, parsed WYSIWYG editable text])
     */
    public function get_form_fields_for_add()
    {
        return $this->get_form_fields();
    }

    /**
     * Get Tempcode for a gallery adding/editing form.
     *
     * @param  ID_TEXT $name The gallery codename (blank: new)
     * @param  SHORT_TEXT $fullname The full human-readable name of the gallery
     * @param  LONG_TEXT $description The description of the gallery
     * @param  LONG_TEXT $notes Hidden notes associated with the gallery
     * @param  ID_TEXT $parent_id The parent gallery (blank: no parent)
     * @param  ?BINARY $accept_images Whether images may be put in this gallery (null: work out statistically)
     * @param  ?BINARY $accept_videos Whether videos may be put in this gallery (null: work out statistically)
     * @param  BINARY $is_member_synched Whether the gallery serves as a container for automatically created member galleries
     * @param  ?ID_TEXT $layout_mode Whether the gallery uses the grid, carousel or mosaic layout mode (null: pick statistically based on current usage of other galleries)
     * @param  ?URLPATH $rep_image The representative image of the gallery (null: none)
     * @param  ?URLPATH $watermark_top_left Watermark (null: none)
     * @param  ?URLPATH $watermark_top_right Watermark (null: none)
     * @param  ?URLPATH $watermark_bottom_left Watermark (null: none)
     * @param  ?URLPATH $watermark_bottom_right Watermark (null: none)
     * @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  ?SHORT_TEXT $gallery_sort The sort order for sub-galleries (null: use site default)
     * @param  ?SHORT_TEXT $media_sort The sort order for media within the gallery (null: use site default)
     * @return array A pair: The input fields, Hidden fields
     */
    public function get_form_fields(string $name = '', string $fullname = '', string $description = '', string $notes = '', string $parent_id = '', ?int $accept_images = null, ?int $accept_videos = null, int $is_member_synched = 0, ?string $layout_mode = null, ?string $rep_image = null, ?string $watermark_top_left = null, ?string $watermark_top_right = null, ?string $watermark_bottom_left = null, ?string $watermark_bottom_right = null, ?int $allow_rating = null, ?int $allow_comments = null, ?string $gallery_sort = null, ?string $media_sort = null) : array
    {
        list($allow_rating, $allow_comments,) = $this->choose_feedback_fields_statistically($allow_rating, $allow_comments, 1);

        $accept_images = take_param_int_modeavg($accept_images, 'accept_images', 'galleries', 1);
        $accept_videos = take_param_int_modeavg($accept_videos, 'accept_videos', 'galleries', 1);

        $hidden = new Tempcode();

        if ($layout_mode === null) {
            $layout_mode = $GLOBALS['SITE_DB']->query_select_value_if_there('galleries', 'layout_mode', [], 'GROUP BY layout_mode ORDER BY COUNT(layout_mode) DESC');  // Determine default based on what is 'the norm' currently.

            if ($layout_mode === null) {
                // No galleries exist
                $layout_mode = GALLERY_LAYOUT_MODE_DEFAULT;
            }
        }

        $fields = new Tempcode();
        $fields->attach(form_input_line(do_lang_tempcode('TITLE'), do_lang_tempcode('DESCRIPTION_TITLE'), 'fullname', $fullname, true));
        if ($name != 'root') {
            if (get_option('manual_gallery_codename') == '1') {
                $fields->attach(form_input_codename(do_lang_tempcode('CODENAME'), do_lang_tempcode('DESCRIPTION_CODENAME'), 'gallery_name', $name, true));
            } else {
                $hidden->attach(form_input_hidden('gallery_name', $name));
            }
        } else {
            $hidden->attach(form_input_hidden('gallery_name', 'root'));
        }
        $fields->attach(form_input_text_comcode(do_lang_tempcode('DESCRIPTION'), do_lang_tempcode('DESCRIPTION_DESCRIPTION'), 'description', $description, false));
        if ($parent_id == '') {
            $parent_id = get_param_string('parent_id', '');
        }
        if ($name != 'root') {
            if ((get_option('manual_gallery_parent') == '1') || ($parent_id == '')) {
                $parent_gallery_title = '';
                if ($parent_id != '') {
                    $_parent_gallery_title = $GLOBALS['SITE_DB']->query_select_value_if_there('galleries', 'fullname', ['name' => $parent_id]);
                    if ($_parent_gallery_title !== null) {
                        $parent_gallery_title = get_translated_text($_parent_gallery_title);
                    }
                }
                $fields->attach(form_input_tree_list(do_lang_tempcode('PARENT'), do_lang_tempcode('DESCRIPTION_PARENT', 'gallery'), 'parent_id', null, 'choose_gallery', ['filter' => 'only_conventional_galleries', 'addable_filter' => true/*FUDGE: A little naughty, but it encodes roughly what we want and doesn't hurt staff; we have separate enable/disable images/videos settings in galleries, so permissions for adding entries can reasonably be a base requirement for permissions for adding categories*/, 'purity' => true], true, $parent_id, false, null, false, $parent_gallery_title));
            } else {
                $hidden->attach(form_input_hidden('parent_id', $parent_id));
            }
        }
        if ((get_option('manual_gallery_media_types') == '1') || ($accept_images == 0) || ($accept_videos == 0)) {
            $fields->attach(form_input_various_ticks([[do_lang_tempcode('ACCEPT_IMAGES'), 'accept_images', $accept_images == 1, do_lang_tempcode('DESCRIPTION_ACCEPT_IMAGES')], [do_lang_tempcode('ACCEPT_VIDEOS'), 'accept_videos', $accept_videos == 1, do_lang_tempcode('DESCRIPTION_ACCEPT_VIDEOS')]], new Tempcode(), null, do_lang_tempcode('ACCEPTED_MEDIA_TYPES')));
        } else {
            $hidden->attach(form_input_hidden('accept_images', '1'));
            $hidden->attach(form_input_hidden('accept_videos', '1'));
        }
        $gallery_mode_is = get_option('gallery_mode_is');
        if (($name != '') && ($gallery_mode_is !== 'choice') && ($layout_mode !== $gallery_mode_is)) {
            $gallery_mode_is = 'choice'; // Continue current but allow a choice to change
        }
        if ($gallery_mode_is !== 'choice') {
            $hidden->attach(form_input_hidden('layout_mode', $gallery_mode_is));
        } else {
            $radios = form_input_radio_entry('layout_mode', GALLERY_LAYOUT_MODE_GRID, $layout_mode === GALLERY_LAYOUT_MODE_GRID, do_lang_tempcode('GRID_MODE'));
            $radios->attach(form_input_radio_entry('layout_mode', GALLERY_LAYOUT_MODE_CAROUSEL, $layout_mode === GALLERY_LAYOUT_MODE_CAROUSEL, do_lang_tempcode('CAROUSEL_MODE')));
            $radios->attach(form_input_radio_entry('layout_mode', GALLERY_LAYOUT_MODE_MOSAIC, $layout_mode === GALLERY_LAYOUT_MODE_MOSAIC, do_lang_tempcode('MOSAIC_MODE')));
            $fields->attach(form_input_radio(do_lang_tempcode('LAYOUT_MODE'), do_lang_tempcode('DESCRIPTION_LAYOUT_MODE'), 'layout_mode', $radios));
        }

        $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', ['_GUID' => '94d1f77eb9fdca010cb9d1eac5d19b9b', 'SECTION_HIDDEN' => ($rep_image == '') && ($is_member_synched == 0), 'TITLE' => do_lang_tempcode('ADVANCED')]));

        // Per-gallery sort order (uses site config hooks)
        require_code('config2');
        require_code('hooks/systems/config/galleries_sort_order');
        $ob = object_factory('Hook_config_galleries_sort_order');
        $details = $ob->get_details();
        $details['explanation'] = 'PER_GALLERY_SORT_galleries_sort_order';
        if ($details !== null) {
            $fields->attach(build_config_inputter('galleries_sort_order', $details, $gallery_sort, true, false));
        }

        require_code('config2');
        require_code('hooks/systems/config/gallery_media_default_sort_order');
        $ob = object_factory('Hook_config_gallery_media_default_sort_order');
        $details = $ob->get_details();
        $details['explanation'] = 'PER_GALLERY_SORT_gallery_media_default_sort_order';
        if ($details !== null) {
            $fields->attach(build_config_inputter('gallery_media_default_sort_order', $details, $media_sort, true, false));
        }

        $request_rep_image = (get_option('gallery_rep_image') == '1') || ($rep_image != '');
        $request_member_synced = (get_option('gallery_member_synced') == '1') || ($is_member_synched == 1) || ($name == 'root');
        if ($request_rep_image || $request_member_synced) {
            if ($request_rep_image) {
                $fields->attach(form_input_upload_multi_source(do_lang_tempcode('REPRESENTATIVE_IMAGE'), do_lang_tempcode('DESCRIPTION_REPRESENTATIVE_IMAGE_GALLERY'), $hidden, 'image', null, false, $rep_image, false, null, IMAGE_CRITERIA_WEBSAFE));
            }
            if ($request_member_synced) {
                $fields->attach(form_input_tick(do_lang_tempcode('IS_MEMBER_SYNCHED', 'gallery'), do_lang_tempcode('DESCRIPTION_IS_MEMBER_SYNCHED_GALLERY', 'gallery'), 'is_member_synched', $is_member_synched == 1));
            }
        }

        if (get_option('gallery_watermarks') == '1') {
            $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER', ['_GUID' => '555320228b5a1ff1effb8a1bf9c15d8e', 'SECTION_HIDDEN' => ($watermark_top_left === null) && ($watermark_top_right === null) && ($watermark_bottom_left === null) && ($watermark_bottom_right === null), 'TITLE' => do_lang_tempcode('WATERMARKING')]));
            $fields->attach(form_input_upload(do_lang_tempcode('_WATERMARK', do_lang_tempcode('TOP_LEFT')), do_lang_tempcode('_DESCRIPTION_WATERMARK', do_lang_tempcode('TOP_LEFT')), 'watermark_top_left', false, $watermark_top_left, null, true, get_allowed_image_file_types(IMAGE_CRITERIA_GD_READ)));
            $fields->attach(form_input_upload(do_lang_tempcode('_WATERMARK', do_lang_tempcode('TOP_RIGHT')), do_lang_tempcode('_DESCRIPTION_WATERMARK', do_lang_tempcode('TOP_RIGHT')), 'watermark_top_right', false, $watermark_top_right, null, true, get_allowed_image_file_types(IMAGE_CRITERIA_GD_READ)));
            $fields->attach(form_input_upload(do_lang_tempcode('_WATERMARK', do_lang_tempcode('BOTTOM_LEFT')), do_lang_tempcode('_DESCRIPTION_WATERMARK', do_lang_tempcode('BOTTOM_LEFT')), 'watermark_bottom_left', false, $watermark_bottom_left, null, true, get_allowed_image_file_types(IMAGE_CRITERIA_GD_READ)));
            $fields->attach(form_input_upload(do_lang_tempcode('_WATERMARK', do_lang_tempcode('BOTTOM_RIGHT')), do_lang_tempcode('_DESCRIPTION_WATERMARK', do_lang_tempcode('BOTTOM_RIGHT')), 'watermark_bottom_right', false, $watermark_bottom_right, null, true, get_allowed_image_file_types(IMAGE_CRITERIA_GD_READ)));
        }
        handle_max_file_size($hidden, 'image');

        // Permissions
        if (get_option('gallery_permissions') == '1') {
            $fields->attach($this->get_permission_fields($name, null, ($name == '')));
        } else {
            require_code('permissions2');
            $hidden->attach(get_category_permissions_hidden_on());
        }
        if (addon_installed('ecommerce')) {
            require_code('ecommerce_permission_products');
            $fields->attach(permission_product_form('gallery', ($name == '') ? null : $name));
        }

        // Metadata
        require_code('content2');
        $seo_fields = seo_get_fields($this->seo_type, ($name == '') ? null : $name, false);
        if (get_option('gallery_feedback_fields') == '1') {
            require_code('feedback2');
            $feedback_fields = feedback_fields($this->content_type, $allow_rating == 1, $allow_comments == 1, null, false, $notes, $allow_comments == 2, true, true, false);
        } else {
            $hidden->attach(form_input_hidden('allow_rating', strval($allow_rating)));
            $hidden->attach(form_input_hidden('allow_comments', strval($allow_comments)));
            $feedback_fields = new Tempcode();
        }
        $fields->attach(metadata_get_fields('gallery', ($name == '') ? null : $name, false, [], ($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('gallery', ($name == '') ? null : $name));
        }

        return [$fields, $hidden];
    }

    /**
     * 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(string $id) : array
    {
        $rows = $GLOBALS['SITE_DB']->query_select('galleries', ['add_date', 'g_owner'], ['name' => $id], '', 1);
        if (!array_key_exists(0, $rows)) {
            return [get_member_id_from_gallery_name($id), null];
        }
        return [get_member_id_from_gallery_name($id, $rows[0]), $rows[0]['add_date']];
    }

    /**
     * Standard crud_module edit form filler.
     *
     * @param  ID_TEXT $id The category being edited
     * @return mixed Either Tempcode; or a tuple of: (fields, hidden-fields[, delete-fields][, edit-text][, whether all delete fields are specified][, posting form text, more fields][, parsed WYSIWYG editable text])
     */
    public function fill_in_edit_form(string $id)
    {
        $rows = $GLOBALS['SITE_DB']->query_select('galleries', ['*'], ['name' => $id], '', 1);
        if (!array_key_exists(0, $rows)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'gallery'));
        }
        $myrow = $rows[0];

        return $this->get_form_fields($id, get_translated_text($myrow['fullname']), get_translated_text($myrow['the_description']), $myrow['notes'], $myrow['parent_id'], $myrow['accept_images'], $myrow['accept_videos'], $myrow['is_member_synched'], $myrow['layout_mode'], $myrow['rep_image'], $myrow['watermark_top_left'], $myrow['watermark_top_right'], $myrow['watermark_bottom_left'], $myrow['watermark_bottom_right'], $myrow['allow_rating'], $myrow['allow_comments'], $myrow['gallery_sort'], $myrow['media_sort']);
    }

    /**
     * Standard crud_module add actualiser.
     *
     * @return array A pair: The category added, description about usage
     */
    public function add_actualisation() : array
    {
        $name = post_param_string('gallery_name', '');
        $fullname = post_param_string('fullname');
        if ($name == '') {
            $name = preg_replace('#[^' . URL_CONTENT_REGEXP . ']#', '', $fullname);
        }
        $description = post_param_string('description');
        $notes = post_param_string('notes', '');
        $parent_id = post_param_string('parent_id');
        $accept_images = post_param_integer('accept_images', 0);
        $accept_videos = post_param_integer('accept_videos', 0);
        $is_member_synched = post_param_integer('is_member_synched', 0);
        $layout_mode = post_param_string('layout_mode', null);

        require_code('images2');
        $url = post_param_image('image', 'uploads/repimages', null, false);

        $watermark_top_left = get_url('', 'watermark_top_left', 'uploads/watermarks', OBFUSCATE_NEVER, CMS_UPLOAD_IMAGE);
        $watermark_top_right = get_url('', 'watermark_top_right', 'uploads/watermarks', OBFUSCATE_NEVER, CMS_UPLOAD_IMAGE);
        $watermark_bottom_left = get_url('', 'watermark_bottom_left', 'uploads/watermarks', OBFUSCATE_NEVER, CMS_UPLOAD_IMAGE);
        $watermark_bottom_right = get_url('', 'watermark_bottom_right', 'uploads/watermarks', OBFUSCATE_NEVER, CMS_UPLOAD_IMAGE);

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

        $gallery_sort = post_param_string('galleries_sort_order', '');
        $media_sort = post_param_string('gallery_media_default_sort_order', '');

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

        add_gallery($name, $fullname, $description, $notes, $parent_id, $accept_images, $accept_videos, $is_member_synched, $layout_mode, $url, $watermark_top_left[0], $watermark_top_right[0], $watermark_bottom_left[0], $watermark_bottom_right[0], $gallery_sort, $media_sort, $allow_rating, $allow_comments, false, $metadata['add_time'], $metadata['submitter'], '', '', false);

        set_url_moniker('gallery', $name);

        $this->set_permissions($name);
        if (addon_installed('ecommerce')) {
            require_code('ecommerce_permission_products');
            permission_product_save('gallery', $name);
        }

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

        return [$name, null];
    }

    /**
     * Standard crud_module edit actualiser.
     *
     * @param  ID_TEXT $id The category being edited
     * @return ?Tempcode Description about usage (null: none)
     */
    public function edit_actualisation(string $id) : ?object
    {
        $name = post_param_string('gallery_name', fractional_edit() ? $id : '');
        $fullname = post_param_string('fullname');
        if ($name == '') {
            $name = preg_replace('#[^' . URL_CONTENT_REGEXP . ']#', '', $fullname);
        }
        $parent_id = post_param_string('parent_id', STRING_MAGIC_NULL);
        $accept_images = post_param_integer('accept_images', fractional_edit() ? INTEGER_MAGIC_NULL : 0);
        $accept_videos = post_param_integer('accept_videos', fractional_edit() ? INTEGER_MAGIC_NULL : 0);
        $is_member_synched = post_param_integer('is_member_synched', fractional_edit() ? INTEGER_MAGIC_NULL : 0);
        $layout_mode = post_param_string('layout_mode', null);

        if (!fractional_edit()) {
            require_code('images2');
            $url = post_param_image('image', 'uploads/repimages', null, false, true);

            $watermark_top_left = get_url('', 'watermark_top_left', 'uploads/watermarks', OBFUSCATE_NEVER, CMS_UPLOAD_IMAGE);
            if (($watermark_top_left[0] == '') && (post_param_integer('watermark_top_left_unlink', 0) != 1)) {
                $watermark_top_left[0] = null;
            }
            $watermark_top_right = get_url('', 'watermark_top_right', 'uploads/watermarks', OBFUSCATE_NEVER, CMS_UPLOAD_IMAGE);
            if (($watermark_top_right[0] == '') && (post_param_integer('watermark_top_right_unlink', 0) != 1)) {
                $watermark_top_right[0] = null;
            }
            $watermark_bottom_left = get_url('', 'watermark_bottom_left', 'uploads/watermarks', OBFUSCATE_NEVER, CMS_UPLOAD_IMAGE);
            if (($watermark_bottom_left[0] == '') && (post_param_integer('watermark_bottom_left_unlink', 0) != 1)) {
                $watermark_bottom_left[0] = null;
            }
            $watermark_bottom_right = get_url('', 'watermark_bottom_right', 'uploads/watermarks', OBFUSCATE_NEVER, CMS_UPLOAD_IMAGE);
            if (($watermark_bottom_right[0] == '') && (post_param_integer('watermark_bottom_right_unlink', 0) != 1)) {
                $watermark_bottom_right[0] = null;
            }
        } else {
            $url = STRING_MAGIC_NULL;
            $watermark_top_left = STRING_MAGIC_NULL;
            $watermark_top_right = STRING_MAGIC_NULL;
            $watermark_bottom_left = STRING_MAGIC_NULL;
            $watermark_bottom_right = 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);
        $gallery_sort = post_param_string('galleries_sort_order', STRING_MAGIC_NULL);
        $media_sort = post_param_string('gallery_media_default_sort_order', STRING_MAGIC_NULL);

        $metadata = actual_metadata_get_fields('gallery', $id, [], $name);

        edit_gallery(
            $id,
            $name,
            $fullname,
            post_param_string('description', STRING_MAGIC_NULL),
            post_param_string('notes', STRING_MAGIC_NULL),
            $parent_id,
            $accept_images,
            $accept_videos,
            $is_member_synched,
            $layout_mode,
            $url,
            $watermark_top_left[0],
            $watermark_top_right[0],
            $watermark_bottom_left[0],
            $watermark_bottom_right[0],
            $gallery_sort,
            $media_sort,
            post_param_string('meta_keywords', STRING_MAGIC_NULL),
            post_param_string('meta_description', STRING_MAGIC_NULL),
            $allow_rating,
            $allow_comments,
            $metadata['submitter'],
            $metadata['add_time'],
            true,
            false
        );

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

        $this->new_id = $name;

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

        if (!fractional_edit()) {
            $this->set_permissions($name);
            if (addon_installed('ecommerce')) {
                require_code('ecommerce_permission_products');
                permission_product_save('gallery', $id, $name);
            }
        }

        return null;
    }

    /**
     * Standard crud_module delete possibility checker.
     *
     * @param  ID_TEXT $id The category being potentially deleted
     * @return boolean Whether it may be deleted
     */
    public function may_delete_this(string $id) : bool
    {
        return $id != 'root';
    }

    /**
     * Standard crud_module delete actualiser.
     *
     * @param  ID_TEXT $id The category being deleted
     */
    public function delete_actualisation(string $id)
    {
        delete_gallery($id);
    }

    /**
     * The do-next manager for after download content management (event types only).
     *
     * @param  ?Tempcode $title The title (output of get_screen_title) (null: don't use full page)
     * @param  Tempcode $description Some description to show, saying what happened
     * @param  ?ID_TEXT $id The ID of whatever was just handled (null: N/A)
     * @return Tempcode The UI
     */
    public function do_next_manager(?object $title, object $description, ?string $id = null) : object
    {
        return $this->_do_next_manager($title, $description, ($id === null) ? null : $id);
    }

    /**
     * The do-next manager for after image/video content management.
     *
     * @param  ?Tempcode $title The title (output of get_screen_title) (null: don't use full page)
     * @param  Tempcode $description Some description to show, saying what happened
     * @param  ?ID_TEXT $cat The gallery we were working in (null: deleted)
     * @param  ?AUTO_LINK $id The ID of whatever was just handled (null: N/A)
     * @param  boolean $video Whether we were working with a video
     * @return Tempcode The UI
     */
    public function _do_next_manager(?object $title, object $description, ?string $cat = null, ?int $id = null, bool $video = false) : object
    {
        $extra = [];
        if ($cat !== null) {
            $hooks = find_all_hook_obs('modules', 'galleries_users', 'Hook_gu_');
            foreach ($hooks as $object) {
                $extra = array_merge($extra, $object->new_donext_icons($cat));
            }
        }

        require_code('templates_donext');

        if (($id === null) && ($cat === null)) {
            return do_next_manager(
                $title,
                $description,
                [],
                null,
                /* TYPED-ORDERED LIST OF 'LINKS' */
                null, // Add one
                null, // Edit this
                null, // Edit one
                null, // View this
                ['galleries', ['type' => 'browse'], get_module_zone('galleries'), do_lang_tempcode('GALLERIES')], // View archive
                has_privilege(get_member(), 'submit_cat_midrange_content', 'cms_galleries') ? ['_SELF', ['type' => 'add_category'], '_SELF', do_lang('ADD_GALLERY')] : null, // Add one category
                has_privilege(get_member(), 'edit_own_cat_midrange_content', 'cms_galleries') ? ['_SELF', ['type' => 'edit_category'], '_SELF', do_lang('EDIT_GALLERY')] : null, // Edit one category
                ($cat === null) ? null : ['_SELF', ['type' => '_edit_category', 'id' => $cat], '_SELF', do_lang_tempcode('EDIT_THIS_GALLERY')], // Edit this category
                ($cat === null) ? null : ['galleries', ['type' => 'browse', 'id' => $cat], get_module_zone('galleries'), do_lang_tempcode('VIEW_THIS_GALLERY')], // View this category
                /* SPECIALLY TYPED 'LINKS' */
                array_merge($extra, [
                    ['menu/cms/galleries/add_one_image', ['_SELF', ['type' => 'add'], '_SELF'], do_lang('ADD_IMAGE')],
                    ['menu/cms/galleries/add_one_video', ['_SELF', ['type' => 'add_other'], '_SELF'], do_lang('ADD_VIDEO')],
                    has_privilege(get_member(), 'edit_own_midrange_content', 'cms_galleries') ? ['menu/cms/galleries/edit_one_image', ['_SELF', ['type' => 'edit'], '_SELF'], do_lang('EDIT_IMAGE')] : null,
                    has_privilege(get_member(), 'edit_own_midrange_content', 'cms_galleries') ? [(get_option('allow_audio_videos') == '2') ? 'menu/cms/galleries/edit_one_multimedia' : 'menu/cms/galleries/edit_one_video', ['_SELF', ['type' => 'edit_other'], '_SELF'], do_lang('EDIT_VIDEO')] : null,
                ]),
                [],
                [],
                null,
                null,
                null,
                do_lang_tempcode('GALLERIES'),
                $video ? 'video' : 'image',
                'gallery'
            );
        }

        $support_videos = false;
        $support_images = false;
        if ($cat !== null) {
            $cat_row = $GLOBALS['SITE_DB']->query_select('galleries', ['accept_images', 'accept_videos', 'is_member_synched'], ['name' => $cat], '', 1);
            if ((array_key_exists(0, $cat_row)) && ($cat_row[0]['is_member_synched'] == 0)) {
                if ($cat_row[0]['accept_videos'] == 1) {
                    $support_videos = true;
                }
                if ($cat_row[0]['accept_images'] == 1) {
                    $support_images = true;
                }
            }
        }

        return do_next_manager(
            $title,
            $description,
            [],
            null,
            /* TYPED-ORDERED LIST OF 'LINKS' */
            null, // Add one
            (($id === null) || (!has_privilege(get_member(), 'edit_own_midrange_content', 'cms_galleries', ['galleries', $cat]))) ? null : ['_SELF', ['type' => $video ? '_edit_other' : '_edit', 'id' => $id], '_SELF', do_lang_tempcode($video ? 'EDIT_THIS_VIDEO' : 'EDIT_THIS_IMAGE')], // Edit this
            null, // Edit one
            ($id === null) ? null : ['galleries', ['type' => $video ? 'video' : 'image', 'id' => $id, 'wide' => 1], get_module_zone('galleries')], // View this
            ['galleries', ['type' => 'browse'], get_module_zone('galleries'), do_lang_tempcode('GALLERIES')], // View archive
            has_privilege(get_member(), 'submit_cat_midrange_content', 'cms_galleries') ? ['_SELF', ['type' => 'add_category'], '_SELF', do_lang('ADD_GALLERY')] : null, // Add one category
            has_privilege(get_member(), 'edit_own_cat_midrange_content', 'cms_galleries') ? ['_SELF', ['type' => 'edit_category'], '_SELF', do_lang('EDIT_GALLERY')] : null, // Edit one category
            has_privilege(get_member(), 'edit_own_cat_midrange_content', 'cms_galleries') ? ['_SELF', ['type' => '_edit_category', 'id' => $cat], '_SELF', do_lang_tempcode('EDIT_THIS_GALLERY')] : null, // Edit this category
            ['galleries', ['type' => 'browse', 'id' => $cat], get_module_zone('galleries'), do_lang_tempcode('VIEW_THIS_GALLERY')], // View this category
            /* SPECIALLY TYPED 'LINKS' */
            array_merge($extra, [
                [$video ? 'menu/cms/galleries/add_one_video' : 'menu/cms/galleries/add_one_image', ['_SELF', ['type' => $video ? 'add_other' : 'add', 'cat' => ((!$video && !$support_images) || ($video && !$support_videos) || ($cat === null)) ? null : $cat], '_SELF'], do_lang($video ? 'ADD_VIDEO' : 'ADD_IMAGE')],
                [$video ? 'menu/cms/galleries/add_one_image' : 'menu/cms/galleries/add_one_video', ['_SELF', ['type' => $video ? 'add' : 'add_other', 'cat' => (($video && !$support_images) || (!$video && !$support_videos) || ($cat === null)) ? null : $cat], '_SELF'], do_lang($video ? 'ADD_IMAGE' : 'ADD_VIDEO')],
                (has_privilege(get_member(), 'edit_own_midrange_content', 'cms_galleries') ? [$video ? ((get_option('allow_audio_videos') == '2') ? 'menu/cms/galleries/edit_one_multimedia' : 'menu/cms/galleries/edit_one_video') : 'menu/cms/galleries/edit_one_image', ['_SELF', ['type' => $video ? 'edit_other' : 'edit'], '_SELF'], do_lang($video ? 'EDIT_VIDEO' : 'EDIT_IMAGE')] : null), // Edit one
                (has_privilege(get_member(), 'edit_own_midrange_content', 'cms_galleries') ? [$video ? 'menu/cms/galleries/edit_one_image' : ((get_option('allow_audio_videos') == '2') ? 'menu/cms/galleries/edit_one_multimedia' : 'menu/cms/galleries/edit_one_video'), ['_SELF', ['type' => $video ? 'edit' : 'edit_other'], '_SELF'], do_lang($video ? 'EDIT_IMAGE' : 'EDIT_VIDEO')] : null), // Edit one
                has_privilege(get_member(), 'mass_import', 'cms_galleries') ? ['admin/import', ['_SELF', ['type' => '_import', 'name' => $cat], '_SELF'], do_lang('GALLERY_IMPORT')] : null,
            ]),
            [],
            [],
            null,
            null,
            null,
            do_lang_tempcode('GALLERIES'),
            $video ? 'video' : 'image',
            'gallery'
        );
    }
}
