<?php /*

 Composr
 Copyright (c) ocProducts, 2004-2016

 See text/EN/licence.txt for full licencing information.


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

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  ocProducts Ltd
 * @package    calendar
 */

require_code('crud_module');

/**
 * Module page class.
 */
class Module_cms_calendar extends Standard_crud_module
{
    public $lang_type = 'CALENDAR_EVENT';
    public $select_name = 'TITLE';
    public $orderer = 'id';
    public $array_key = 'id';
    public $title_is_multi_lang = true;
    public $non_integer_id = false;
    public $table = 'calendar_events';
    public $code_require = 'calendar';
    public $permissions_require = 'low';
    public $user_facing = true;
    public $seo_type = 'event';
    public $content_type = 'event';
    public $possibly_some_kind_of_upload = true;
    public $menu_label = 'CALENDAR';
    public $permissions_cat_require = 'calendar';
    public $permissions_cat_name = 'type';
    public $posting_field_required = false;

    // These are state-set, for usage by the specialised donext manager
    public $donext_type = null;
    public $donext_date = null;

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

        $ret = array(
            'browse' => array('MANAGE_CALENDARS', 'menu/rich_content/calendar'),
        ) + parent::get_entry_points();

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

        return $ret;
    }

    /**
     * Find privileges defined as overridable by this module.
     *
     * @return array A map of privileges that are overridable; privilege to 0 or 1. 0 means "not category overridable". 1 means "category overridable".
     */
    public function get_privilege_overrides()
    {
        require_lang('calendar');
        return array(
            'submit_cat_highrange_content' => array(0, 'ADD_EVENT_TYPE'),
            'edit_cat_highrange_content' => array(0, 'EDIT_EVENT_TYPE'),
            'delete_cat_highrange_content' => array(0, 'DELETE_EVENT_TYPE'),
            'submit_lowrange_content' => array(1, 'ADD_CALENDAR_EVENT'),

            'bypass_validation_lowrange_content' => array(1, 'BYPASS_VALIDATION_CALENDAR_EVENT__NON_PUBLIC'),
            'edit_own_lowrange_content' => array(1, 'EDIT_OWN_CALENDAR_EVENT__NON_PUBLIC'),
            'edit_lowrange_content' => array(1, 'EDIT_CALENDAR_EVENT__NON_PUBLIC'),
            'delete_own_lowrange_content' => array(1, 'DELETE_OWN_CALENDAR_EVENT__NON_PUBLIC'),
            'delete_lowrange_content' => array(1, 'DELETE_CALENDAR_EVENT__NON_PUBLIC'),

            'bypass_validation_midrange_content' => array(1, 'BYPASS_VALIDATION_CALENDAR_EVENT'),
            'edit_own_midrange_content' => array(1, 'EDIT_OWN_CALENDAR_EVENT'),
            'edit_midrange_content' => array(1, 'EDIT_CALENDAR_EVENT'),
            'delete_own_midrange_content' => array(1, 'DELETE_OWN_CALENDAR_EVENT'),
            'delete_midrange_content' => array(1, 'DELETE_CALENDAR_EVENT'),

            'view_private_content' => 0,

            'mass_import' => 0,
        );
    }

    public $title;

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

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

        require_lang('calendar');

        inform_non_canonical_parameter('date');
        inform_non_canonical_parameter('e_type');
        inform_non_canonical_parameter('validated');

        set_helper_panel_tutorial('tut_calendar');

        if ($type == '_import_ical') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:import', do_lang_tempcode('IMPORT_ICAL'))));
            breadcrumb_set_self(do_lang_tempcode('DONE'));
        }

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

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

        return parent::pre_run($top_level);
    }

    /**
     * Standard crud_module run_start.
     *
     * @param  ID_TEXT $type The type of module execution
     * @return Tempcode The output of the run
     */
    public function run_start($type)
    {
        $this->javascript = "
            var form=document.getElementById('recurrence_pattern').form;

            var start=document.getElementById('start');
            var start_day=document.getElementById('start_day');
            var start_month=document.getElementById('start_month');
            var start_year=document.getElementById('start_year');
            var start_time=document.getElementById('start_time');
            var start_hour=document.getElementById('start_hour');
            var start_minute=document.getElementById('start_minute');
            var end_time=document.getElementById('end_time');
            var end_hour=document.getElementById('end_hour');
            var end_minute=document.getElementById('end_minute');
            var do_timezone_conv=document.getElementById('do_timezone_conv');
            var all_day_event=document.getElementById('all_day_event');

            var crf=function(event) {
                var s=(form.elements['recurrence'][0].checked);
                if (form.elements['recurrence_pattern']) form.elements['recurrence_pattern'].disabled=s;
                if (form.elements['recurrences']) form.elements['recurrences'].disabled=s;
                if (form.elements['seg_recurrences']) form.elements['seg_recurrences'].disabled=s;

                var has_date_set=false;
                if (start_day)
                {
                    has_date_set=(start_day.selectedIndex!=0) && (start_month.selectedIndex!=0) && (start_year.selectedIndex!=0);
                } else
                {
                    has_date_set=(start.value!='');
                }

                if ((typeof event!='undefined') && (has_date_set)) // Something changed
                {
                    var url='calendar_recurrence_suggest';
                    url+='&monthly_spec_type='+window.encodeURIComponent(radio_value(form.elements['monthly_spec_type']));
                    if (start_day)
                    {
                        url+='&date_day='+window.encodeURIComponent(start_day.options[start_day.selectedIndex].value);
                        url+='&date_month='+window.encodeURIComponent(start_month.options[start_month.selectedIndex].value);
                        url+='&date_year='+window.encodeURIComponent(start_year.options[start_year.selectedIndex].value);
                    } else
                    {
                        url+='&date='+window.encodeURIComponent(start.value);
                    }
                    if (start_hour)
                    {
                        url+='&date_time_hour='+window.encodeURIComponent(start_hour.options[start_hour.selectedIndex].value);
                        url+='&date_time_minute='+window.encodeURIComponent(start_minute.options[start_minute.selectedIndex].value);
                    } else
                    {
                        url+='&date_time='+window.encodeURIComponent(start_time.value);
                    }
                    url+='&do_timezone_conv='+(do_timezone_conv && do_timezone_conv.checked?'1':'0');
                    url+='&all_day_event='+(all_day_event.checked?'1':'0');
                    var new_data=load_snippet(url);
                    var tr=form.elements['monthly_spec_type'][0];
                    while (tr.nodeName.toLowerCase()!='tr')
                    {
                        tr=tr.parentNode;
                    }
                    set_inner_html(tr,new_data.replace(/<tr [^>]*>/,'').replace(/<\/tr>/,''));
                }
                var monthly_recurrence=form.elements['recurrence'][3].checked;
                for (var i=0;i<form.elements['monthly_spec_type'].length;i++)
                {
                    form.elements['monthly_spec_type'][i].disabled=!monthly_recurrence;
                }
            };
            crf();
            for (var i=0;i<form.elements['recurrence'].length;i++) form.elements['recurrence'][i].onclick=crf;
            if (start_day)
            {
                start_day.onchange=crf;
                start_month.onchange=crf;
                start_year.onchange=crf;
            } else
            {
                start.onchange=crf;
            }
            if (start_hour)
            {
                start_hour.onchange=crf;
                start_minute.onchange=crf;
            } else
            {
                start_time.onchange=crf;
            }

            var crf2=function() {
                var s=document.getElementById('all_day_event').checked;
                if (start_hour)
                {
                    start_hour.disabled=s;
                    start_minute.disabled=s;
                } else
                {
                    start_time.disabled=s;
                }
                if (end_hour)
                {
                    end_hour.disabled=s;
                    end_minute.disabled=s;
                } else
                {
                    end_time.disabled=s;
                }
            }
            crf2();
            document.getElementById('all_day_event').onclick=crf2;

            form.old_submit=form.onsubmit;
            form.onsubmit=function() {
                if (typeof form.elements['end_day']!='undefined' && form.elements['end_day'].selectedIndex!=0 || typeof form.elements['end']!='undefined' && form.elements['end'].value!='')
                {
                    var start_date,end_date;
                    if (start_day)
                    {
                        start_date=new Date(window.parseInt(form.elements['start_year'].value),window.parseInt(form.elements['start_month'].value)-1,window.parseInt(form.elements['start_day'].value),window.parseInt(form.elements['start_hour'].value),window.parseInt(form.elements['start_minute'].value));
                        end_date=new Date(window.parseInt(form.elements['end_year'].value),window.parseInt(form.elements['end_month'].value)-1,window.parseInt(form.elements['end_day'].value),window.parseInt(form.elements['end_hour'].value),window.parseInt(form.elements['end_minute'].value));
                    } else
                    {
                        start_date=start.value;
                        end_date=end.value;
                    }

                    if (start_date>end_date)
                    {
                        window.fauxmodal_alert('" . php_addslashes(do_lang('EVENT_CANNOT_AROUND')) . "');
                        return false;
                    }
                }
                if (typeof form.old_submit!='undefined' && form.old_submit) return form.old_submit();
                return true;
            };
        ";

        $this->posting_form_title = do_lang_tempcode('EVENT_TEXT');
        $this->posting_form_description = do_lang_tempcode('DESCRIPTION_EVENT_TEXT');

        require_lang('dates');
        require_css('calendar');
        require_code('calendar2');

        $private = get_param_integer('private', post_param_integer('relay__private', null));
        if ($private !== null) {
            $this->lang_type = ($private == 1) ? 'PRIVATE_CALENDAR_EVENT' : 'PUBLIC_CALENDAR_EVENT';
        }

        // Decide what to do
        if ($type == 'browse') {
            return $this->browse();
        }
        if ($type == 'import') {
            return $this->import_ical();
        }
        if ($type == '_import_ical') {
            return $this->_import_ical();
        }
        if ($type == 'export') {
            return $this->export_ical();
        }
        if ($type == '_export') {
            return $this->_export_ical();
        }

        return new Tempcode();
    }

    /**
     * The do-next manager for before content management.
     *
     * @return Tempcode The UI
     */
    public function browse()
    {
        require_code('templates_donext');
        require_code('fields');
        return do_next_manager(get_screen_title('MANAGE_CALENDARS'), comcode_lang_string('DOC_CALENDAR'),
            array_merge(array(
                has_privilege(get_member(), 'submit_cat_highrange_content', 'cms_calendar') ? array('menu/_generic_admin/add_one_category', array('_SELF', array('type' => 'add_category'), '_SELF'), do_lang('ADD_EVENT_TYPE')) : null,
                has_privilege(get_member(), 'edit_own_cat_highrange_content', 'cms_calendar') ? array('menu/_generic_admin/edit_one_category', array('_SELF', array('type' => 'edit_category'), '_SELF'), do_lang('EDIT_EVENT_TYPE')) : null,
                has_privilege(get_member(), 'submit_lowrange_content', 'cms_calendar') ? array('menu/_generic_admin/add_one', array('_SELF', array('type' => 'add'), '_SELF'), do_lang('ADD_CALENDAR_EVENT')) : null,
                has_privilege(get_member(), 'edit_own_lowrange_content', 'cms_calendar') ? array('menu/_generic_admin/edit_one', array('_SELF', array('type' => 'edit'), '_SELF'), do_lang('EDIT_CALENDAR_EVENT')) : null,
                has_privilege(get_member(), 'mass_import', 'cms_calendar') ? array('menu/_generic_admin/import', array('_SELF', array('type' => 'import'), '_SELF'), do_lang('IMPORT_ICAL')) : null,
                array('menu/_generic_admin/export', array('_SELF', array('type' => 'export'), '_SELF'), do_lang('EXPORT_ICAL')),
            ), manage_custom_fields_donext_link('event')),
            do_lang('MANAGE_CALENDARS')
        );
    }

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

        $current_ordering = get_param_string('sort', 'e_title ASC', true);
        if (strpos($current_ordering, ' ') === false) {
            warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
        }
        list($sortable, $sort_order) = explode(' ', $current_ordering, 2);
        $sortables = array(
            'e_title' => do_lang_tempcode('TITLE'),
            'e_start_year,e_start_month,e_start_day,e_start_hour,e_start_minute' => do_lang_tempcode('DATE_TIME'),
            'e_type' => do_lang_tempcode('TYPE'),
            'validated' => do_lang_tempcode('VALIDATED'),
        );
        if (($sortable == 'e_start_year,e_start_month,e_start_day,e_start_hour,e_start_minute') && ($sort_order == 'DESC')) {
            $sortable = 'e_start_year DESC,e_start_month DESC,e_start_day DESC,e_start_hour DESC,e_start_minute';
        } else {
            if (((strtoupper($sort_order) != 'ASC') && (strtoupper($sort_order) != 'DESC')) || (!array_key_exists($sortable, $sortables))) {
                log_hack_attack_and_exit('ORDERBY_HACK');
            }
        }

        $header_row = results_field_title(array(
            do_lang_tempcode('TITLE'),
            do_lang_tempcode('DATE_TIME'),
            do_lang_tempcode('TYPE'),
            protect_from_escaping(do_template('COMCODE_ABBR', array('_GUID' => 'fb1dd2bde0067a726dfc9443c873a5f1', 'TITLE' => do_lang_tempcode('VALIDATED'), 'CONTENT' => do_lang_tempcode('VALIDATED_SHORT')))),
            do_lang_tempcode('ACTIONS'),
        ), $sortables, 'sort', $sortable . ' ' . $sort_order);

        $fields = new Tempcode();

        require_code('form_templates');
        $only_owned = has_privilege(get_member(), 'edit_lowrange_content', 'cms_calendar') ? null : get_member();
        list($rows, $max_rows) = $this->get_entry_rows(false, $current_ordering, (is_null($only_owned) ? array() : array('e_submitter' => $only_owned)));
        $types = array();
        foreach ($rows as $row) {
            $edit_link = build_url($url_map + array('id' => $row['id']), '_SELF');

            if (array_key_exists($row['e_type'], $types)) {
                $type = $types[$row['e_type']];
            } else {
                $_type = $GLOBALS['SITE_DB']->query_select_value_if_there('calendar_types', 't_title', array('id' => $row['e_type']));
                $type = ($_type === null) ? do_lang('UNKNOWN') : get_translated_text($_type);
                $types[$row['e_type']] = $type;
            }

            list(, $time_raw) = find_event_start_timestamp($row);
            $date = get_timezoned_date($time_raw, !is_null($row['e_start_hour']), false, false, true);

            $fields->attach(results_entry(array(protect_from_escaping(hyperlink(build_url(array('page' => 'calendar', 'type' => 'view', 'id' => $row['id']), get_module_zone('calendar')), get_translated_text($row['e_title']), false, true)), $date, $type, ($row['validated'] == 1) ? do_lang_tempcode('YES') : do_lang_tempcode('NO'), protect_from_escaping(hyperlink($edit_link, do_lang_tempcode('EDIT'), false, true, do_lang('EDIT') . ' #' . strval($row['id'])))), true));
        }

        $search_url = build_url(array('page' => 'search', 'id' => 'calendar'), get_module_zone('search'));
        $archive_url = build_url(array('page' => 'calendar'), get_module_zone('calendar'));

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

    /**
     * Standard crud_module list function.
     *
     * @return Tempcode The selection list
     */
    public function create_selection_list_entries()
    {
        $only_owned = has_privilege(get_member(), 'edit_lowrange_content', 'cms_calendar') ? null : get_member();
        return create_selection_list_events($only_owned, null, has_privilege(get_member(), 'edit_midrange_content', 'cms_calendar'));
    }

    /**
     * Get the form fields for an event input form.
     *
     * @param  ?AUTO_LINK $id The event ID (null: new)
     * @param  ?AUTO_LINK $type The event type (null: default)
     * @param  ?integer $start_year The year the event starts at (null: default)
     * @param  ?integer $start_month The month the event starts at (null: default)
     * @param  ?integer $start_day The day the event starts at (null: default)
     * @param  ID_TEXT $start_monthly_spec_type In-month specification type for start date
     * @set day_of_month day_of_month_backwards dow_of_month dow_of_month_backwards
     * @param  ?integer $start_hour The hour the event starts at (null: default)
     * @param  ?integer $start_minute The minute the event starts at (null: default)
     * @param  SHORT_TEXT $title The title of the event
     * @param  LONG_TEXT $content The full text describing the event
     * @param  SHORT_TEXT $recurrence The recurrence code
     * @param  ?integer $recurrences The number of recurrences (null: none/infinite)
     * @param  BINARY $seg_recurrences Whether to segregate the comment-topics/rating/trackbacks per-recurrence
     * @param  integer $priority The priority
     * @range  1 5
     * @param  ?integer $end_year The year the event ends at (null: not a multi day event)
     * @param  ?integer $end_month The month the event ends at (null: not a multi day event)
     * @param  ?integer $end_day The day the event ends at (null: not a multi day event)
     * @param  ID_TEXT $end_monthly_spec_type In-month specification type for end date
     * @set day_of_month day_of_month_backwards dow_of_month dow_of_month_backwards
     * @param  ?integer $end_hour The hour the event ends at (null: not a multi day event)
     * @param  ?integer $end_minute The minute the event ends at (null: not a multi day event)
     * @param  ?ID_TEXT $timezone The timezone for the event (null: current user's timezone)
     * @param  BINARY $do_timezone_conv Whether the time should be presented in the viewer's own timezone
     * @param  ?MEMBER $member_calendar The member's calendar it will be on (null: not on a specific member's calendar)
     * @param  BINARY $validated Whether the event is validated
     * @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
     * @param  ?array $regions The regions (empty: not region-limited) (null: same as empty)
     * @return array A tuple of: (fields, hidden-fields, delete-fields, edit-text, whether all delete fields are specified, posting form text, more fields)
     */
    public function get_form_fields($id = null, $type = null, $start_year = null, $start_month = null, $start_day = null, $start_monthly_spec_type = 'day_of_month', $start_hour = null, $start_minute = null, $title = '', $content = '', $recurrence = 'none', $recurrences = null, $seg_recurrences = 0, $priority = 3, $end_year = null, $end_month = null, $end_day = null, $end_monthly_spec_type = 'day_of_month', $end_hour = null, $end_minute = null, $timezone = null, $do_timezone_conv = 0, $member_calendar = null, $validated = 1, $allow_rating = null, $allow_comments = null, $allow_trackbacks = null, $notes = '', $regions = null)
    {
        list($allow_rating, $allow_comments, $allow_trackbacks) = $this->choose_feedback_fields_statistically($allow_rating, $allow_comments, $allow_trackbacks);

        unset($content);

        if ((is_null($timezone)) || ($timezone == '')) {
            $timezone = get_users_timezone();
        }

        require_code('form_templates');

        if (is_null($type)) { // Adding one
            $date = get_param_string('date', '');
            $start_hour = null;
            $start_minute = null;
            if ($date != '') {
                $date2 = explode(' ', $date);
                $exploded = explode('-', $date2[0]);
                //if (count($exploded)!=3) warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
                $start_year = intval($exploded[0]);
                $start_month = intval($exploded[1]);
                $start_day = intval($exploded[2]);
                if (array_key_exists(1, $date2)) {
                    $exploded = explode(':', $date2[1]);
                    //if (count($exploded)!=2) warn_exit(do_lang_tempcode('INTERNAL_ERROR'));

                    $start_hour = intval($exploded[0]);
                    $start_minute = intval($exploded[1]);
                }
            } else {
                list($start_year, $start_month, $start_day) = array(null, null, null);
            }
            $type = get_param_integer('e_type', null);

            // Get a nice default title for the event
            $calendar_member_id = is_null($member_calendar) ? get_member() : $member_calendar;
            $index = $GLOBALS['SITE_DB']->query_value_if_there('SELECT COUNT(*) FROM ' . get_table_prefix() . 'calendar_events WHERE e_submitter=' . strval($calendar_member_id) . ' OR e_member_calendar=' . strval($calendar_member_id)) + 1;
            $calendar_username = $GLOBALS['FORUM_DRIVER']->get_username($calendar_member_id, true);
            $abbreviation = get_ordinal_suffix($index);
            $title = do_lang('AUTO_EVENT_TITLE', $calendar_username, strval($index), $abbreviation);

            $adding = true;
        } else {
            $adding = false;
        }

        if (is_null($type)) {
            $_type = get_value('default_event_type');
            if (is_null($_type)) {
                $type = db_get_first_id() + 1;
            } else {
                $type = intval($_type);
            }
        }
        if (is_null($start_month)) {
            $start_year = intval(date('Y'));
            $start_month = intval(date('m'));
            $start_day = intval(date('d'));
            $start_hour = null;
            $start_minute = null;
        }
        if ((is_null($end_year) || is_null($end_month) || is_null($end_day)) && ($adding)) {
            $end_year = null;//$start_year;
            $end_month = null;//$start_month;
            $end_day = null;//$start_day;
            $end_hour = null;
            $end_minute = null;
        }

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

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

        // Dates...

        $fields->attach(form_input_tick(do_lang_tempcode('ALL_DAY_EVENT'), do_lang_tempcode('DESCRIPTION_ALL_DAY_EVENT'), 'all_day_event', $start_hour === null));

        $_start_hour = ($start_hour === null) ? find_timezone_start_hour_in_utc($timezone, $start_year, $start_month, $start_day, $start_monthly_spec_type) : $start_hour;
        $_start_minute = ($start_minute === null) ? find_timezone_start_minute_in_utc($timezone, $start_year, $start_month, $start_day, $start_monthly_spec_type) : $start_minute;
        $start_day_of_month = find_concrete_day_of_month($start_year, $start_month, $start_day, $start_monthly_spec_type, $_start_hour, $_start_minute, $timezone, $do_timezone_conv == 1);
        $_start_time = normalise_time_array(array($_start_minute, $_start_hour, $start_month, $start_day_of_month, $start_year), $timezone);
        $_start_date_field = form_input_date(
            do_lang_tempcode('DATE_TIME'), // $pretty_name
            '', // $description
            'start', // $name
            true, // $required
            false, // $null_default
            true, // $do_time
            $_start_time, // $default_time
            120, // $total_years_to_show
            intval(date('Y')) - 100, // $year_start
            null, // $tabindex
            true, // $do_date
            $timezone, // $timezone
            get_param_string('date', '') == '' // $handle_timezone
        );
        $fields->attach($_start_date_field);

        $_end_hour = ($end_hour === null) ? find_timezone_end_hour_in_utc($timezone, $end_year, $end_month, $end_day, $end_monthly_spec_type) : $end_hour;
        $_end_minute = ($end_minute === null) ? find_timezone_end_minute_in_utc($timezone, $end_year, $end_month, $end_day, $end_monthly_spec_type) : $end_minute;
        $end_day_of_month = find_concrete_day_of_month($end_year, $end_month, $end_day, $end_monthly_spec_type, $_end_hour, $_end_minute, $timezone, $do_timezone_conv == 1);
        $_end_time = normalise_time_array(array($_end_minute, $_end_hour, $end_month, $end_day_of_month, $end_year), $timezone);
        $_end_date_field = form_input_date(
            do_lang_tempcode('END_DATE_AND_TIME'), // $pretty_name
            do_lang_tempcode('DESCRIPTION_END_DATE_AND_TIME'), // $description
            'end', // $name
            false, // $required
            $end_year === null, // $null_default
            true, // $do_time
            $_end_time, // $default_time
            120, // $total_years_to_show
            intval(date('Y')) - 100, // $year_start
            null, // $tabindex
            true, // $do_date
            $timezone, // $timezone
            get_param_string('date', '') == '' // $handle_timezone
        );
        $fields->attach($_end_date_field);

        // ---

        // Type
        $type_list = create_selection_list_event_types($type);
        if ($type_list->is_empty()) {
            warn_exit(do_lang_tempcode('NO_CATEGORIES', 'calendar_type'));
        }
        $fields->attach(form_input_list(do_lang_tempcode('TYPE'), do_lang_tempcode('DESCRIPTION_EVENT_TYPE'), 'type', $type_list));

        // Priority
        $priority_list = new Tempcode();
        $priority_list->attach(form_input_list_entry('1', $priority == 1, do_lang_tempcode('PRIORITY_1')));
        $priority_list->attach(form_input_list_entry('2', $priority == 2, do_lang_tempcode('PRIORITY_2')));
        $priority_list->attach(form_input_list_entry('3', $priority == 3, do_lang_tempcode('PRIORITY_3')));
        $priority_list->attach(form_input_list_entry('4', $priority == 4, do_lang_tempcode('PRIORITY_4')));
        $priority_list->attach(form_input_list_entry('5', $priority == 5, do_lang_tempcode('PRIORITY_5')));
        $fields->attach(form_input_list(do_lang_tempcode('PRIORITY'), '', 'priority', $priority_list));

        // Validation
        if ($validated == 0) {
            $validated = get_param_integer('validated', 0);
        }
        if (has_some_cat_privilege(get_member(), 'bypass_validation_' . $this->permissions_require . 'range_content', null, $this->permissions_cat_require)) {
            if (addon_installed('unvalidated')) {
                $fields->attach(form_input_tick(do_lang_tempcode('VALIDATED'), do_lang_tempcode($GLOBALS['FORUM_DRIVER']->is_super_admin(get_member()) ? 'DESCRIPTION_VALIDATED_SIMPLE' : 'DESCRIPTION_VALIDATED', 'event'), 'validated', $validated == 1));
            }
        }

        $fields2 = new Tempcode();

        // Member calendar
        if ($member_calendar === null) {
            if ((has_privilege(get_member(), 'add_public_events'))/*Doesn't need it prepopulated*/ || (is_guest())) {
                $_member_calendar = '';
            } else {
                $_member_calendar = $GLOBALS['FORUM_DRIVER']->get_username(get_member()); // Has to be filled, so prepopulate
            }
        } else {
            $_member_calendar = $GLOBALS['FORUM_DRIVER']->get_username($member_calendar);
        }
        if (has_privilege(get_member(), 'calendar_add_to_others') || $member_calendar == get_member()) {
            $fields2->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => 'c40661dec7b2e3a59114d1dd846890ff', 'SECTION_HIDDEN' => $member_calendar === null, 'TITLE' => do_lang_tempcode('ADVANCED'))));

            $_member_calendar = is_null($member_calendar) ? '' : $GLOBALS['FORUM_DRIVER']->get_username($member_calendar);
            $fields2->attach(form_input_username(do_lang_tempcode('MEMBER_CALENDAR'), do_lang_tempcode('DESCRIPTION_MEMBER_CALENDAR'), 'member_calendar', $_member_calendar, !has_privilege(get_member(), 'add_public_events')));
        }

        if ((get_option('allow_international') !== '0') || (get_option('filter_regions') == '1')) {
            $fields2->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => 'fd78d3298730d0cb157b20f1b3dd6ae1', 'SECTION_HIDDEN' => true, 'TITLE' => do_lang_tempcode('TIMEZONE'))));
        }

        // More date stuff
        if (get_option('allow_international') !== '0') {
            $list = '';
            foreach (get_timezone_list() as $_timezone => $timezone_nice) {
                $list .= static_evaluate_tempcode(form_input_list_entry($_timezone, $_timezone == $timezone, $timezone_nice));
            }
            $fields2->attach(form_input_list(do_lang_tempcode('TIMEZONE'), do_lang_tempcode('DESCRIPTION_EVENT_TIMEZONE'), 'timezone', make_string_tempcode($list)));
            $fields2->attach(form_input_tick(do_lang_tempcode('DO_TIMEZONE_CONV'), do_lang_tempcode('DESCRIPTION_DO_TIMEZONE_CONV'), 'do_timezone_conv', $do_timezone_conv == 1));
        } else {
            /*
            This causes severe confusion
            $hidden->attach(form_input_hidden('timezone', $timezone));
            $hidden->attach(form_input_hidden('do_timezone_conv', strval($do_timezone_conv)));
            */
            $hidden->attach(form_input_hidden('timezone', get_site_timezone()));
            $hidden->attach(form_input_hidden('do_timezone_conv', '0'));
        }

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

        // Recurrence
        $fields2->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => '313be8d71088bf12c9c5e5f67f28174a', 'SECTION_HIDDEN' => ($recurrence == 'none'), 'TITLE' => do_lang_tempcode('RECURRENCE'))));
        if (strpos($recurrence, ' ') === false) {
            $recurrence_main = $recurrence;
            $recurrence_pattern = '';
        } else {
            list($recurrence_main, $recurrence_pattern) = explode(' ', $recurrence);
        }
        $radios = form_input_radio_entry('recurrence', 'none', true, do_lang_tempcode('NA_EM'));
        $radios->attach(form_input_radio_entry('recurrence', 'daily', $recurrence_main == 'daily', do_lang_tempcode('DAILY')));
        $radios->attach(form_input_radio_entry('recurrence', 'weekly', $recurrence_main == 'weekly', do_lang_tempcode('WEEKLY')));
        $radios->attach(form_input_radio_entry('recurrence', 'monthly', $recurrence_main == 'monthly', do_lang_tempcode('MONTHLY')));
        $radios->attach(form_input_radio_entry('recurrence', 'yearly', $recurrence_main == 'yearly', do_lang_tempcode('YEARLY')));
        $fields2->attach(form_input_radio(do_lang_tempcode('RECURRENCE'), do_lang_tempcode('DESCRIPTION_RECURRENCE'), 'recurrence', $radios)); // xth_dotw_of_monthly
        $fields2->attach(form_input_line(do_lang_tempcode('RECURRENCE_PATTERN'), do_lang_tempcode('DESCRIPTION_RECURRENCE_PATTERN'), 'recurrence_pattern', $recurrence_pattern, false));
        $fields2->attach(form_input_integer(do_lang_tempcode('RECURRENCES'), do_lang_tempcode('DESCRIPTION_RECURRENCES'), 'recurrences', $recurrences, false));
        $fields2->attach(form_input_tick(do_lang_tempcode('SEG_RECURRENCES'), do_lang_tempcode('DESCRIPTION_SEG_RECURRENCES'), 'seg_recurrences', $seg_recurrences == 1));
        $concrete_start_day = find_concrete_day_of_month($start_year, $start_month, $start_day, $start_monthly_spec_type, $start_hour, $start_minute, $timezone, $do_timezone_conv == 1);
        $start_time = mktime(($start_hour === null) ? 12 : $start_hour, ($start_hour === null) ? 0 : $start_minute, 0, $start_month, $concrete_start_day, $start_year);
        $start_time = tz_time($start_time, get_users_timezone());
        $conv_month = intval(date('n', $start_time));
        $conv_day = intval(date('j', $start_time));
        $conv_year = intval(date('Y', $start_time));
        $fields2->attach(monthly_spec_type_chooser($conv_day, $conv_month, $conv_year, $start_monthly_spec_type));

        if (($adding) && (cron_installed()) && (has_privilege(get_member(), 'set_reminders'))) { // Some more stuff only when adding
            $fields2->attach(do_template('FORM_SCREEN_FIELD_SPACER', array('_GUID' => 'd2117ffcebeb2125e77a31e4d5cfd9bb', 'SECTION_HIDDEN' => true, 'TITLE' => do_lang_tempcode('REMINDERS'))));

            $fields2->attach(form_input_tick(do_lang_tempcode('SIGN_UP_REMINDER'), do_lang_tempcode('DESCRIPTION_SIGN_UP_REMINDER'), 'sign_up_reminder', true));
            if (has_privilege(get_member(), 'add_public_events')) {
                $usergroup_list = $GLOBALS['FORUM_DRIVER']->get_usergroup_list(true);
                if (get_forum_type() == 'cns') {
                    unset($usergroup_list[db_get_first_id()]);
                }
                $t_usergroup_list = new Tempcode();
                foreach ($usergroup_list as $id => $name) {
                    $t_usergroup_list->attach(form_input_list_entry(strval($id), false, $name));
                }
                $fields2->attach(form_input_all_and_not(do_lang_tempcode('SIGN_UP_REMINDER_GROUPS'), do_lang_tempcode('DESCRIPTION_SIGN_UP_REMINDER_GROUPS'), 'sign_up_reminder_groups', $t_usergroup_list));
            }
            $fields2->attach(form_input_float(do_lang_tempcode('REMINDER_TIME'), do_lang_tempcode('DESCRIPTION_REMINDER_TIME'), 'hours_before', 1.0, true));
        }

        require_code('activities');
        $fields2->attach(get_syndication_option_fields('event'));

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

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

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

        // Relay control parameters
        $relay__private = get_param_integer('private', null);
        if ($relay__private !== null) {
            $hidden->attach(form_input_hidden('relay__private', strval($relay__private)));
        }
        $relay__member_id = get_param_integer('member_id', null);
        if (($relay__member_id !== null) && ($relay__member_id != get_member())) {
            if ($relay__member_id != get_member()) {
                enforce_personal_access($relay__member_id, null, 'calendar_add_to_others');
            }
            $hidden->attach(form_input_hidden('relay__member_id', strval($relay__member_id)));
        }

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

    /**
     * Get the form posted parameters specifying an event.
     *
     * @return array A list of parameters in a certain order (see the return command to see the order)
     */
    public function get_event_parameters()
    {
        $type = post_param_integer('type', fractional_edit() ? INTEGER_MAGIC_NULL : false);
        if ((!has_actual_page_access(get_member(), 'admin_commandr')) && ($type == db_get_first_id())) {
            access_denied('I_ERROR');
        }

        $title = post_param_string('title');

        $content = post_param_string('post', STRING_MAGIC_NULL);

        $priority = post_param_integer('priority', fractional_edit() ? INTEGER_MAGIC_NULL : 3);

        $recurrence = post_param_string('recurrence', STRING_MAGIC_NULL);
        if (($recurrence != 'none') && ($recurrence != STRING_MAGIC_NULL)) {
            $recurrence_pattern = post_param_string('recurrence_pattern', '');
            if ($recurrence_pattern != '') {
                $recurrence .= ' ' . $recurrence_pattern;
            }
        }
        $recurrences = post_param_integer('recurrences', fractional_edit() ? INTEGER_MAGIC_NULL : null);

        $timezone = post_param_string('timezone', STRING_MAGIC_NULL);
        $do_timezone_conv = post_param_integer('do_timezone_conv', fractional_edit() ? INTEGER_MAGIC_NULL : 0);

        $_member_calendar = post_param_string('member_calendar', '');
        if ((has_privilege(get_member(), 'calendar_add_to_others')) || ($_member_calendar == $GLOBALS['FORUM_DRIVER']->get_username(get_member()))) {
            if ($_member_calendar != '') {
                $member_calendar = $GLOBALS['FORUM_DRIVER']->get_member_from_username($_member_calendar);
                if (($member_calendar === null) || (is_guest($member_calendar))) {
                    warn_exit(do_lang_tempcode('_MEMBER_NO_EXIST', escape_html($_member_calendar)));
                }
            } else {
                $member_calendar = mixed();
            }
        } else {
            $member_calendar = mixed();
        }
        if ((!has_privilege(get_member(), 'add_public_events')) && (is_null($member_calendar))) {
            warn_exit(do_lang_tempcode('MEMBER_NO_EXIST'));
        }

        $start_monthly_spec_type = post_param_string('start_monthly_spec_type', post_param_string('monthly_spec_type', 'day_of_month')); // We actually don't suppose separate spec-types for the ends and starts in the UI
        $start = post_param_date('start');
        if (is_null($start)) {
            $start_year = INTEGER_MAGIC_NULL;
            $start_month = INTEGER_MAGIC_NULL;
            $start_day = INTEGER_MAGIC_NULL;
            $start_hour = INTEGER_MAGIC_NULL;
            $start_minute = INTEGER_MAGIC_NULL;
            $start_monthly_spec_type = STRING_MAGIC_NULL;
        } else {
            if (strpos($start_monthly_spec_type, 'dow') === false) {
                $start_year = intval(date('Y', $start));
                $start_month = intval(date('m', $start));
                $start_day = intval(date('d', $start));
            } else { // Has to be encoded in timezone, no conversion
                require_code('temporal2');
                list($start_year, $start_month, $start_day) = post_param_date_components('start');
            }
            if (post_param_integer('all_day_event', 0) == 1) {
                $start_hour = null;
                $start_minute = null;
            } else {
                $start_hour = intval(date('H', $start));
                $start_minute = intval(date('i', $start));
            }
            if ($start_monthly_spec_type != 'day_of_month') {
                require_code('temporal2');
                list($_start_year, $_start_month, $_start_day) = post_param_date_components('start');

                $start_day = find_abstract_day($_start_year, $_start_month, $_start_day, $start_monthly_spec_type);
            }
        }
        if (fractional_edit()) {
            $end_year = INTEGER_MAGIC_NULL;
            $end_month = INTEGER_MAGIC_NULL;
            $end_day = INTEGER_MAGIC_NULL;
            $end_hour = INTEGER_MAGIC_NULL;
            $end_minute = INTEGER_MAGIC_NULL;
            $end_monthly_spec_type = STRING_MAGIC_NULL;
        } else {
            $end_monthly_spec_type = post_param_string('end_monthly_spec_type', $start_monthly_spec_type);
            $end = post_param_date('end');
            if (!is_null($end)) {
                if (strpos($end_monthly_spec_type, 'dow') === false) {
                    $end_year = intval(date('Y', $end));
                    $end_month = intval(date('m', $end));
                    $end_day = intval(date('d', $end));
                } else { // Has to be encoded in timezone, no conversion
                    require_code('temporal2');
                    list($end_year, $end_month, $end_day) = post_param_date_components('end');
                }
                if (post_param_integer('all_day_event', 0) == 1) {
                    $end_hour = null;
                    $end_minute = null;
                } else {
                    $end_hour = intval(date('H', $end));
                    $end_minute = intval(date('i', $end));
                }

                if ($end_monthly_spec_type != 'day_of_month') {
                    require_code('temporal2');
                    list($_end_year, $_end_month, $_end_day) = post_param_date_components('end');

                    $end_day = find_abstract_day($_end_year, $_end_month, $_end_day, $end_monthly_spec_type);
                } else {
                    // Error if wrong way around
                    if ($start > $end) {
                        warn_exit(do_lang_tempcode('EVENT_CANNOT_AROUND'));
                    }
                }
            } else {
                $end_year = null;
                $end_month = null;
                $end_day = null;
                $end_hour = null;
                $end_minute = null;
                $end_monthly_spec_type = 'day_of_month';
            }
        }

        if (substr($recurrence, 0, strlen('monthly')) != 'monthly') {
            $start_monthly_spec_type = fractional_edit() ? STRING_MAGIC_NULL : 'day_of_month';
            $end_monthly_spec_type = fractional_edit() ? STRING_MAGIC_NULL : 'day_of_month';
        }

        return array($type, $recurrence, $recurrences, $title, $content, $priority, $start_year, $start_month, $start_day, $start_monthly_spec_type, $start_hour, $start_minute, $end_year, $end_month, $end_day, $end_monthly_spec_type, $end_hour, $end_minute, $timezone, $do_timezone_conv, $member_calendar);
    }

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

    /**
     * Standard crud_module cat getter.
     *
     * @param  ID_TEXT $id The entry for which the cat is sought
     * @return mixed The cat
     */
    public function get_cat($id)
    {
        $temp = $GLOBALS['SITE_DB']->query_select_value_if_there('calendar_events', 'e_type', array('id' => intval($id)));
        if (is_null($temp)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'event'));
        }
        return $temp;
    }

    /**
     * Standard crud_module edit form filler.
     *
     * @param  ID_TEXT $id The entry being edited
     * @return array 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($id)
    {
        $rows = $GLOBALS['SITE_DB']->query_select('calendar_events', array('*'), array('id' => intval($id)), '', 1);
        if (!array_key_exists(0, $rows)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'event'));
        }
        $myrow = $rows[0];

        $parsed = get_translated_tempcode('calendar_events', $myrow, 'e_content');

        check_edit_permission(($myrow['e_member_calendar'] === null) ? 'mid' : 'low', $myrow['e_submitter']);

        $content = get_translated_text($myrow['e_content']);

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

        $fields = $this->get_form_fields($myrow['id'], $myrow['e_type'], $myrow['e_start_year'], $myrow['e_start_month'], $myrow['e_start_day'], $myrow['e_start_monthly_spec_type'], $myrow['e_start_hour'], $myrow['e_start_minute'], get_translated_text($myrow['e_title']), $content, $myrow['e_recurrence'], $myrow['e_recurrences'], $myrow['e_seg_recurrences'], $myrow['e_priority'], $myrow['e_end_year'], $myrow['e_end_month'], $myrow['e_end_day'], $myrow['e_end_monthly_spec_type'], $myrow['e_end_hour'], $myrow['e_end_minute'], $myrow['e_timezone'], $myrow['e_do_timezone_conv'], $myrow['e_member_calendar'], $myrow['validated'], $myrow['allow_rating'], $myrow['allow_comments'], $myrow['allow_trackbacks'], $myrow['notes'], $regions);

        if (has_delete_permission('low', get_member(), $myrow['e_submitter'], 'cms_calendar')) {
            $radios = form_input_radio_entry('delete', '0', true, do_lang_tempcode('EDIT_ALL_RECURRENCES'));
            $radios->attach(form_input_radio_entry('delete', '3', false, do_lang_tempcode('FIX_PAST_RECURRENCES')));
            $radios->attach(form_input_radio_entry('delete', '1', false, do_lang_tempcode('DELETE')));
            $delete_fields = form_input_radio(do_lang_tempcode('ACTION'), do_lang_tempcode('DESCRIPTION_FIX_PAST_RECURRENCES'), 'delete', $radios);
        } else {
            $delete_fields = new Tempcode();
        }

        return array($fields[0], $fields[1], $delete_fields, '', true, $content, $fields[6], $parsed);
    }

    /**
     * Standard crud_module add actualiser.
     *
     * @return array A pair: the entry added, and a description
     */
    public function add_actualisation()
    {
        list($type, $recurrence, $recurrences, $title, $content, $priority, $start_year, $start_month, $start_day, $start_monthly_spec_type, $start_hour, $start_minute, $end_year, $end_month, $end_day, $end_monthly_spec_type, $end_hour, $end_minute, $timezone, $do_timezone_conv, $member_calendar) = $this->get_event_parameters();

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

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

        $conflicts = detect_conflicts(get_member(), null, $start_year, $start_month, $start_day, $start_monthly_spec_type, $start_hour, $start_minute, $end_year, $end_month, $end_day, $start_monthly_spec_type, $end_hour, $end_minute, $recurrence, $recurrences, $type, $member_calendar, DETECT_CONFLICT_SCOPE_ALL);
        $_description = is_null($conflicts) ? paragraph(do_lang_tempcode('SUBMIT_THANKYOU')) : $conflicts;

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

        $id = add_calendar_event($type, $recurrence, $recurrences, $seg_recurrences, $title, $content, $priority, $start_year, $start_month, $start_day, $start_monthly_spec_type, $start_hour, $start_minute, $end_year, $end_month, $end_day, $end_monthly_spec_type, $end_hour, $end_minute, $timezone, $do_timezone_conv, $member_calendar, $validated, $allow_rating, $allow_comments, $allow_trackbacks, $notes, $metadata['submitter'], $metadata['views'], $metadata['add_time'], $metadata['edit_time'], null, '', '', $regions);

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

        // Reminders
        if (has_privilege(get_member(), 'set_reminders')) {
            if (php_function_allowed('set_time_limit')) {
                @set_time_limit(0);
            }
            $rem_groups = array();
            if ((has_privilege(get_member(), 'add_public_events')) && (array_key_exists('sign_up_reminder_groups', $_POST))) {
                $all_groups = $GLOBALS['FORUM_DRIVER']->get_usergroup_list(true);
                require_code('form_templates');
                $multi_code = read_multi_code('sign_up_reminder_groups'); // Usergroups signed up
                require_code('selectcode');
                if ((substr($multi_code, 0, 1) == '-') || (substr($multi_code, 0, 1) == '*')) {
                    $rem_groups = $all_groups;
                    if (get_forum_type() == 'cns') {
                        unset($rem_groups[db_get_first_id()]);
                    }
                }
                foreach (explode(',', substr($multi_code, 1)) as $m) {
                    if (substr($multi_code, 0, 1) == '-') {
                        unset($rem_groups[intval($m)]);
                    } elseif (substr($multi_code, 0, 1) == '+') {
                        $rem_groups[intval($m)] = $all_groups[intval($m)];
                    }
                }
                $rem_groups = array_keys($rem_groups);
            }
            $start = 0;
            do {
                $members = array();
                if (count($rem_groups) != 0) {
                    $members = array_keys($GLOBALS['FORUM_DRIVER']->member_group_query($rem_groups, 300, $start));
                    $members = array_diff($members, array(get_member(), $GLOBALS['FORUM_DRIVER']->get_guest_id()));
                }
                if (($start == 0) && (post_param_integer('sign_up_reminder', 0) == 1)) {// If this member is signing up
                    $members[] = get_member();
                }
                if (count($members) != 0) { // Now add their reminders
                    $secs_before = float_unformat(post_param_string('hours_before', '1.0')) * 3600.0;

                    $filled1 = array();
                    $filled2 = array();
                    $filled3 = array();
                    foreach ($members as $member) {
                        // Privacy
                        $privacy_ok = true;
                        if (addon_installed('content_privacy')) {
                            require_code('content_privacy');
                            if (!has_privacy_access('event', strval($id), $member)) {
                                $privacy_ok = false;
                            }
                        }

                        if ($privacy_ok) {
                            $filled1[] = $id;
                            $filled2[] = intval($secs_before);
                            $filled3[] = $member;
                        }
                    }
                    $GLOBALS['SITE_DB']->query_insert('calendar_reminders', array(
                        'e_id' => $filled1,
                        'n_seconds_before' => $filled2,
                        'n_member_id' => $filled3,
                    ));
                }
                $start += 300;
            } while (array_key_exists(0, $members));

            $start = 0;
            do {
                $members = array();
                $interested = $GLOBALS['SITE_DB']->query_select('calendar_interests', array('i_member_id'), array('t_type' => $type), '', 300, $start);
                foreach ($interested as $int) { // Members with declarations of interest
                    if (!in_array($int['i_member_id'], $members)) {
                        $members[] = $int['i_member_id'];
                    }
                }
                $members = array_diff($members, array(get_member(), $GLOBALS['FORUM_DRIVER']->get_guest_id()));
                foreach ($members as $member) { // Now add their reminders. Can't do this as multi-insert as there may be dupes, so we need to skip over errors individually
                    // Privacy
                    $privacy_ok = true;
                    if (addon_installed('content_privacy')) {
                        require_code('content_privacy');
                        if (!has_privacy_access('event', strval($id), $member)) {
                            $privacy_ok = false;
                        }
                    }

                    if ($privacy_ok) {
                        $secs_before = float_unformat(post_param_string('hours_before', '1.0')) * 3600.0;
                        $GLOBALS['SITE_DB']->query_insert('calendar_reminders', array(
                            'e_id' => $id,
                            'n_member_id' => $member,
                            'n_seconds_before' => intval($secs_before),
                        ), false, true);
                    }
                }
                $start += 300;
            } while (array_key_exists(0, $members));
        }

        regenerate_event_reminder_jobs($id);

        $this->donext_type = $type;
        $_start_hour = ($start_hour === null) ? find_timezone_start_hour_in_utc($timezone, $start_year, $start_month, $start_day, $start_monthly_spec_type) : $start_hour;
        $_start_minute = ($start_minute === null) ? find_timezone_start_minute_in_utc($timezone, $start_year, $start_month, $start_day, $start_monthly_spec_type) : $start_minute;
        $start_day_of_month = find_concrete_day_of_month($start_year, $start_month, $start_day, $start_monthly_spec_type, $_start_hour, $_start_minute, $timezone, $do_timezone_conv == 1);
        $this->donext_date = strval($start_year) . '-' . strval($start_month) . '-' . strval($start_day_of_month);

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

        if (($validated == 1) || (!addon_installed('unvalidated'))) {
            if ((has_actual_page_access(get_modal_user(), 'calendar')) && (has_category_access(get_modal_user(), 'calendar', strval($type)))) {
                $privacy_ok = true;
                if (addon_installed('content_privacy')) {
                    require_code('content_privacy');
                    $privacy_ok = has_privacy_access('event', strval($id), $GLOBALS['FORUM_DRIVER']->get_guest_id());
                }
                if ($privacy_ok) {
                    list($date_range) = get_calendar_event_first_date($timezone, $do_timezone_conv, $start_year, $start_month, $start_day, $start_monthly_spec_type, $start_hour, $start_minute, $end_year, $end_month, $end_day, $end_monthly_spec_type, $end_hour, $end_minute, $recurrence, $recurrences);
                    if ($recurrence != '') {
                        $date_range = do_lang('DOES_RECUR', $date_range);
                    }

                    require_code('activities');
                    syndicate_described_activity('calendar:ACTIVITY_CALENDAR_EVENT', $title, $date_range, '', '_SEARCH:calendar:view:' . strval($id), '', '', 'calendar', 1, null, true);
                }
            }
        }

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

        return array(strval($id), $_description);
    }

    /**
     * Standard crud_module edit actualiser.
     *
     * @param  ID_TEXT $_id The entry being edited
     * @return Tempcode Description shown after editing
     */
    public function edit_actualisation($_id)
    {
        $id = intval($_id);

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

        check_edit_permission(($event['e_member_calendar'] === null) ? 'mid' : 'low', $event['e_submitter']);

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

        list($type, $recurrence, $_recurrences, $title, $content, $priority, $_start_year, $_start_month, $_start_day, $start_monthly_spec_type, $_start_hour, $_start_minute, $_end_year, $_end_month, $_end_day, $end_monthly_spec_type, $_end_hour, $_end_minute, $timezone, $do_timezone_conv, $member_calendar) = $this->get_event_parameters();

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

        $fixed_past = false;

        // Fixing past occurrences
        if (($delete_status == '3') && (!fractional_edit())) {
            // Fix past occurrences
            $past_times = find_periods_recurrence($event['e_timezone'], 1, $event['e_start_year'], $event['e_start_month'], $event['e_start_day'], $event['e_start_monthly_spec_type'], $event['e_start_hour'], $event['e_start_minute'], $event['e_end_year'], $event['e_end_month'], $event['e_end_day'], $event['e_end_monthly_spec_type'], $event['e_end_hour'], $event['e_end_minute'], $event['e_recurrence'], $event['e_recurrences'], utctime_to_usertime(mktime($event['e_start_hour'], $event['e_start_minute'], 0, $event['e_start_month'], $event['e_start_day'], $event['e_start_year'])), utctime_to_usertime(time()));
            if (count($past_times) > 0) {
                foreach ($past_times as $past_time) {
                    list($start_year, $start_month, $start_day, $start_hour, $start_minute) = explode('-', date('Y-m-d-h-i', usertime_to_utctime($past_time[0])));
                    if (is_null($event['e_end_day'])) {
                        list($end_year, $end_month, $end_day, $end_hour, $end_minute) = array(null, null, null, null, null);
                    } else {
                        $explode = explode('-', date('Y-m-d-h-i', usertime_to_utctime($past_time[1])));
                        $end_year = intval($explode[0]);
                        $end_month = intval($explode[1]);
                        $end_day = intval($explode[2]);
                        $end_hour = intval($explode[3]);
                        $end_minute = intval($explode[4]);
                    }
                    add_calendar_event($event['e_type'], 'none', null, 0, get_translated_text($event['e_title']), get_translated_text($event['e_content']), $event['e_priority'], intval($start_year), intval($start_month), intval($start_day), 'day_of_month', intval($start_hour), intval($start_minute), $end_year, $end_month, $end_day, 'day_of_month', $end_hour, $end_minute, $timezone, $do_timezone_conv, $member_calendar, $validated, $allow_rating, $allow_comments, $allow_trackbacks, $notes);
                }
                if (is_null($_recurrences)) {
                    $recurrences = null;
                } else {
                    $recurrences = max(0, $_recurrences - count($past_times));
                }

                // Find next occurrence in future
                if (count($past_times) == 0) {
                    $start_year = $_start_year;
                    $start_month = $_start_month;
                    $start_day = $_start_day;
                    $start_hour = $_start_hour;
                    $start_minute = $_start_minute;
                    $end_year = $_end_year;
                    $end_month = $_end_month;
                    $end_day = $_end_day;
                    $end_hour = $_end_hour;
                    $end_minute = $_end_minute;
                }
                $past_times = find_periods_recurrence($event['e_timezone'], 1, $start_year, $start_month, $start_day, $start_monthly_spec_type, $start_hour, $start_minute, $end_year, $end_month, $end_day, $end_monthly_spec_type, $end_hour, $end_minute, $event['e_recurrence'], usertime_to_utctime($past_times[0][0] + 1));
                if (array_key_exists(0, $past_times)) {
                    $past_time = $past_times[0];
                    $explode = explode('-', date('Y-m-d-h-i', $past_time[0]));
                    $start_year = intval($explode[0]);
                    $start_month = intval($explode[1]);
                    $start_day = intval($explode[2]);
                    $start_hour = intval($explode[3]);
                    $start_minute = intval($explode[4]);

                    if (is_null($event['e_end_day'])) {
                        list($end_year, $end_month, $end_day, $end_hour, $end_minute) = array(null, null, null, null, null);
                    } else {
                        $explode = explode('-', date('Y-m-d-h-i', $past_time[1]));
                        $end_year = intval($explode[0]);
                        $end_month = intval($explode[1]);
                        $end_day = intval($explode[2]);
                        $end_hour = intval($explode[3]);
                        $end_minute = intval($explode[4]);
                    }
                } else {
                    $recurrences = 0;
                }

                $fixed_past = true;
            }
        }

        if (!$fixed_past) {
            $start_year = $_start_year;
            $start_month = $_start_month;
            $start_day = $_start_day;
            $start_hour = $_start_hour;
            $start_minute = $_start_minute;
            $end_year = $_end_year;
            $end_month = $_end_month;
            $end_day = $_end_day;
            $end_hour = $_end_hour;
            $end_minute = $_end_minute;

            $recurrences = $_recurrences;
        }

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

        if (($validated == 1) && ($GLOBALS['SITE_DB']->query_select_value_if_there('calendar_events', 'validated', array('id' => $id)) === 0)) { // Just became validated, syndicate as just added
            if ((has_actual_page_access(get_modal_user(), 'calendar')) && (has_category_access(get_modal_user(), 'calendar', strval($type)))) {
                $privacy_ok = true;
                if (addon_installed('content_privacy')) {
                    require_code('content_privacy');
                    $privacy_ok = has_privacy_access('event', strval($id), $GLOBALS['FORUM_DRIVER']->get_guest_id());
                }
                if ($privacy_ok) {
                    list($date_range) = get_calendar_event_first_date($timezone, $do_timezone_conv, $start_year, $start_month, $start_day, $start_monthly_spec_type, $start_hour, $start_minute, $end_year, $end_month, $end_day, $end_monthly_spec_type, $end_hour, $end_minute, $recurrence, $recurrences);
                    if ($recurrence != '') {
                        $date_range = do_lang('DOES_RECUR', $date_range);
                    }

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

                    require_code('activities');
                    syndicate_described_activity(($submitter != get_member()) ? 'calendar:ACTIVITY_VALIDATE_CALENDAR_EVENT' : 'calendar:ACTIVITY_CALENDAR_EVENT', $title, $date_range, '', '_SEARCH:calendar:view:' . strval($id), '', '', 'calendar', 1, null/*$submitter*/, true);
                }
            }
        }

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

        if (!fractional_edit()) {
            $conflicts = detect_conflicts(get_member(), $id, $start_year, $start_month, $start_day, $start_monthly_spec_type, $start_hour, $start_minute, $end_year, $end_month, $end_day, $end_monthly_spec_type, $end_hour, $end_minute, $recurrence, $recurrences, $type, $member_calendar, DETECT_CONFLICT_SCOPE_ALL);
            $_description = is_null($conflicts) ? paragraph(do_lang_tempcode('SUCCESS')) : $conflicts;
        } else {
            $_description = do_lang_tempcode('SUCCESS');
        }

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

        edit_calendar_event($id, $type, $recurrence, $recurrences, $seg_recurrences, $title, $content, $priority, $start_year, $start_month, $start_day, $start_monthly_spec_type, $start_hour, $start_minute, $end_year, $end_month, $end_day, $end_monthly_spec_type, $end_hour, $end_minute, $timezone, $do_timezone_conv, $member_calendar, post_param_string('meta_keywords', STRING_MAGIC_NULL), post_param_string('meta_description', STRING_MAGIC_NULL), $validated, $allow_rating, $allow_comments, $allow_trackbacks, $notes, $metadata['edit_time'], $metadata['add_time'], $metadata['views'], $metadata['submitter'], $regions, true);

        if (!fractional_edit()) {
            regenerate_event_reminder_jobs($id);
        }

        $this->donext_type = $type;
        $_start_hour = ($start_hour === null) ? find_timezone_start_hour_in_utc($timezone, $start_year, $start_month, $start_day, $start_monthly_spec_type) : $start_hour;
        $_start_minute = ($start_minute === null) ? find_timezone_start_minute_in_utc($timezone, $start_year, $start_month, $start_day, $start_monthly_spec_type) : $start_minute;
        $start_day_of_month = find_concrete_day_of_month($start_year, $start_month, $start_day, $start_monthly_spec_type, $_start_hour, $_start_minute, $timezone, $do_timezone_conv == 1);
        $this->donext_date = strval($start_year) . '-' . strval($start_month) . '-' . strval($start_day_of_month);

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

        return $_description;
    }

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

        $rows = $GLOBALS['SITE_DB']->query_select('calendar_events', array('*'), array('id' => $id), '', 1);
        if (!array_key_exists(0, $rows)) {
            warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'event'));
        }
        $event = $rows[0];
        check_delete_permission(($event['e_member_calendar'] === null) ? 'mid' : 'low', $event['e_submitter']);

        delete_calendar_event($id);

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

    /**
     * The do-next manager for after calendar content management (events only).
     *
     * @param  Tempcode $title The title (output of get_screen_title)
     * @param  Tempcode $description Some description to show, saying what happened
     * @param  ?AUTO_LINK $id The ID of whatever was just handled (null: N/A)
     * @return Tempcode The UI
     */
    public function do_next_manager($title, $description, $id)
    {
        return $this->cat_crud_module->_do_next_manager($title, $description, is_null($id) ? null : intval($id), $this->donext_type, $this->donext_date);
    }

    /**
     * The UI to import ical for calendar
     *
     * @return Tempcode The UI
     */
    public function import_ical()
    {
        check_privilege('mass_import');

        $lang = post_param_string('lang', user_lang());

        require_code('calendar_ical');

        $post_url = build_url(array('page' => '_SELF', 'type' => '_import_ical', 'old_type' => get_param_string('type', '')), '_SELF');
        $submit_name = do_lang_tempcode('IMPORT_ICAL');

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

        $fields->attach(form_input_upload(do_lang_tempcode('UPLOAD'), do_lang_tempcode('DESCRIPTION_ICAL'), 'file_anytype', false, null, null, true, 'ics,ical'));

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

        return do_template('FORM_SCREEN', array('_GUID' => '5a970cd3766a6d32b64015b4dfd4b25e', 'TITLE' => $this->title, 'TEXT' => do_lang_tempcode('IMPORT_ICAL_TEXT'), 'HIDDEN' => $hidden, 'FIELDS' => $fields, 'SUBMIT_ICON' => 'menu___generic_admin__import', 'SUBMIT_NAME' => $submit_name, 'URL' => $post_url));
    }

    /**
     * The actualiser to import ical for calendar
     *
     * @return Tempcode The UI
     */
    public function _import_ical()
    {
        check_privilege('mass_import');

        set_mass_import_mode();

        require_code('calendar_ical');

        $ical_url = post_param_string('ical_feed_url', null);

        require_code('uploads');
        if (((is_plupload(true)) && (array_key_exists('file_anytype', $_FILES))) || ((array_key_exists('file_anytype', $_FILES)) && (is_uploaded_file($_FILES['file_anytype']['tmp_name'])))) {
            $ical_url = $_FILES['file_anytype']['tmp_name'];
        }

        if (is_null($ical_url)) {
            warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'));
        }

        ical_import($ical_url);

        decache('side_calendar');

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

    /**
     * UI to display export screen fields
     *
     * @return Tempcode The UI
     */
    public function export_ical()
    {
        $fields = new Tempcode();
        $type_list = create_selection_list_event_types();

        //Add all cal option
        $type_list->attach(form_input_list_entry('0', true, 'All types'));

        $fields->attach(form_input_list(do_lang_tempcode('TYPE'), do_lang_tempcode('DESCRIPTION_EVENT_TYPE'), 'type_filter', $type_list));
        $post_url = build_url(array('page' => '_SELF', 'type' => '_export'), '_SELF');
        $submit_name = do_lang_tempcode('EXPORT_ICAL');

        return do_template('FORM_SCREEN', array('_GUID' => 'fa308cb6dd4f82f5ed2dd2fab45b0fef', 'TITLE' => $this->title, 'TEXT' => do_lang_tempcode('EXPORT_ICAL_TEXT'), 'HIDDEN' => '', 'FIELDS' => $fields, 'SUBMIT_ICON' => 'menu___generic_admin__export', 'SUBMIT_NAME' => $submit_name, 'URL' => $post_url, 'GET' => true));
    }

    /**
     * The actualiser to export ical
     */
    public function _export_ical()
    {
        require_code('calendar_ical');
        output_ical();
    }
}

/**
 * Module page class.
 */
class Module_cms_calendar_cat extends Standard_crud_module
{
    public $lang_type = 'EVENT_TYPE';
    public $select_name = 'EVENT_TYPE';
    public $select_name_description = 'DESCRIPTION_EVENT_TYPE';
    public $orderer = 't_title';
    public $array_key = 'id';
    public $title_is_multi_lang = true;
    public $non_integer_id = false;
    public $protect_first = 2;
    public $table = 'calendar_types';
    public $permissions_require = 'cat_high';
    public $permission_module = 'calendar';
    public $menu_label = 'CALENDAR';
    public $is_chained_with_parent_browse = true;

    /**
     * Get Tempcode for a post template adding/editing form.
     *
     * @param  ?AUTO_LINK $id ID of category (null: new category)
     * @param  SHORT_TEXT $title The title
     * @param  SHORT_TEXT $logo The theme image code
     * @param  URLPATH $external_feed URL to external feed to associate with this event type
     * @return array A pair: The input fields, Hidden fields
     */
    public function get_form_fields($id = null, $title = '', $logo = '', $external_feed = '')
    {
        $fields = new Tempcode();
        $hidden = new Tempcode();

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

        require_code('themes2');
        $ids = get_all_image_ids_type('calendar');

        $set_name = 'image';
        $required = true;
        $set_title = do_lang_tempcode('IMAGE');
        $field_set = (count($ids) == 0) ? new Tempcode() : alternate_fields_set__start($set_name);

        $field_set->attach(form_input_upload(do_lang_tempcode('UPLOAD'), '', 'image', $required, null, null, true, str_replace(' ', '', get_option('valid_images'))));

        $image_chooser_field = form_input_theme_image(do_lang_tempcode('STOCK'), '', 'theme_img_code', $ids, null, $logo, null, false);
        $field_set->attach($image_chooser_field);

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

        handle_max_file_size($hidden, 'image');

        if (addon_installed('syndication_blocks')) {
            $fields->attach(form_input_line(do_lang_tempcode('EXTERNAL_FEED'), do_lang_tempcode('DESCRIPTION_EXTERNAL_FEED'), 'external_feed', $external_feed, false));
        } else {
            $hidden->attach(form_input_hidden('external_feed', $external_feed));
        }

        $fields->attach(metadata_get_fields('calendar_type', is_null($id) ? null : strval($id)));
        require_code('seo2');

        // Permissions
        $fields->attach($this->get_permission_fields(is_null($id) ? null : strval($id), null, ($title == '')));

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

        return array($fields, $hidden);
    }

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

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

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

        $fields = new Tempcode();

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

            $total = $GLOBALS['SITE_DB']->query_select_value('calendar_events', 'COUNT(*)', array('e_type' => $row['id']));

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

        $search_url = null;
        $archive_url = null;

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

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

        $ret = $this->get_form_fields(intval($id), get_translated_text($r['t_title']), $r['t_logo'], $r['t_external_feed']);

        return $ret;
    }

    /**
     * Standard crud_module add actualiser.
     *
     * @return ID_TEXT The entry added
     */
    public function add_actualisation()
    {
        $metadata = actual_metadata_get_fields('calendar_type', null);

        require_code('themes2');
        $logo = post_param_theme_img_code('calendar', false, 'image', 'theme_img_code');

        $id = add_event_type(post_param_string('title'), $logo, post_param_string('external_feed'));

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

        $this->set_permissions(strval($id));

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

        return strval($id);
    }

    /**
     * Standard crud_module edit actualiser.
     *
     * @param  ID_TEXT $id The entry being edited
     */
    public function edit_actualisation($id)
    {
        $metadata = actual_metadata_get_fields('calendar_type', $id);

        if (fractional_edit()) {
            $logo = STRING_MAGIC_NULL;
        } else {
            require_code('themes2');
            $logo = post_param_theme_img_code('calendar', false, 'image', 'theme_img_code');
        }

        edit_event_type(intval($id), post_param_string('title'), $logo, post_param_string('external_feed', STRING_MAGIC_NULL));

        if (!fractional_edit()) {
            $this->set_permissions($id);
        }

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

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

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

    /**
     * The do-next manager for after calendar content management.
     *
     * @param  Tempcode $title The title (output of get_screen_title)
     * @param  Tempcode $description Some description to show, saying what happened
     * @param  ?AUTO_LINK $id The ID of whatever was just handled (null: N/A)
     * @param  ?AUTO_LINK $type The category ID we were working in (null: N/A)
     * @param  string $date The Y-m-d of the added/edited event (first occurrence) (blank: whatever)
     * @return Tempcode The UI
     */
    public function _do_next_manager($title, $description, $id, $type, $date)
    {
        $archive_map = array('type' => 'browse', 'view' => 'day');
        if ($date != '') {
            $archive_map['id'] = $date;
        }

        require_code('templates_donext');

        $extra = array();
        $relay__private = post_param_integer('relay__private', null);
        if ($relay__private !== null) {
            $extra['private'] = $relay__private;
        }
        $relay__member_id = post_param_integer('relay__member_id', null);
        if ($relay__member_id !== null) {
            $extra['member_id'] = $relay__member_id;
        }

        if ((is_null($id)) && (is_null($type))) {
            return do_next_manager($title, $description,
                null,
                null,
                /* TYPED-ORDERED LIST OF 'LINKS'    */
                array('_SELF', array('type' => 'add'), '_SELF', do_lang_tempcode('ADD_CALENDAR_EVENT')), // Add one
                null, // Edit this
                has_privilege(get_member(), 'edit_own_lowrange_content', 'cms_calendar') ? array('_SELF', array('type' => 'edit'), '_SELF', do_lang_tempcode('EDIT_CALENDAR_EVENT')) : null, // Edit one
                null, // View this
                array('calendar', $archive_map + $extra, get_module_zone('calendar'), do_lang('CALENDAR')), // View archive
                null, // Add to category
                has_privilege(get_member(), 'submit_cat_highrange_content', 'cms_calendar') ? array('_SELF', array('type' => 'add_category') + $extra, '_SELF', do_lang_tempcode('ADD_EVENT_TYPE')) : null, // Add one category
                has_privilege(get_member(), 'edit_own_cat_highrange_content', 'cms_calendar') ? array('_SELF', array('type' => 'edit_category') + $extra, '_SELF', do_lang_tempcode('EDIT_EVENT_TYPE')) : null, // Edit one category
                null, // Edit this category
                null, // View this category
                null,
                null,
                null,
                null,
                null,
                null,
                do_lang_tempcode('EVENT_TYPES'),
                'event',
                'calendar_type'
            );
        }

        return do_next_manager($title, $description,
            null,
            null,
            /* TYPED-ORDERED LIST OF 'LINKS'  */
            array('_SELF', array('type' => 'add', 'e_type' => $type) + $extra, '_SELF', do_lang_tempcode('ADD_CALENDAR_EVENT')), // Add one
            (is_null($id) || (!has_privilege(get_member(), 'edit_own_lowrange_content', 'cms_calendar', array('calendar', 'type')))) ? null : array('_SELF', array('type' => '_edit', 'id' => $id) + $extra, '_SELF', do_lang_tempcode('EDIT_THIS_CALENDAR_EVENT')), // Edit this
            has_privilege(get_member(), 'edit_own_lowrange_content', 'cms_calendar') ? array('_SELF', array('type' => 'edit') + $extra, '_SELF', do_lang_tempcode('EDIT_CALENDAR_EVENT')) : null, // Edit one
            is_null($id) ? null : array('calendar', array('type' => 'view', 'id' => $id) + $extra, get_module_zone('calendar')), // View this
            array('calendar', $archive_map + $extra, get_module_zone('calendar'), do_lang('CALENDAR')), // View archive
            null, // Add to category
            has_privilege(get_member(), 'submit_cat_highrange_content', 'cms_calendar') ? array('_SELF', array('type' => 'add_category') + $extra, '_SELF', do_lang_tempcode('ADD_EVENT_TYPE')) : null, // Add one category
            has_privilege(get_member(), 'edit_own_cat_highrange_content', 'cms_calendar') ? array('_SELF', array('type' => 'edit_category') + $extra, '_SELF', do_lang_tempcode('EDIT_EVENT_TYPE')) : null, // Edit one category
            has_privilege(get_member(), 'edit_own_cat_highrange_content', 'cms_calendar', array('calendar', 'type')) ? array('_SELF', array('type' => '_edit_category', 'id' => $type) + $extra, '_SELF', do_lang_tempcode('EDIT_THIS_EVENT_TYPE')) : null, // Edit this category
            null, // View this category
            null,
            null,
            null,
            null,
            null,
            null,
            do_lang_tempcode('EVENT_TYPES'),
            'event',
            'calendar_type'
        );
    }
}
