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

/*
Note that all this code operates in "user time". That is, the timestamps being banded around are not as the server would recognise them, because they encode offsets to match the users timezone settings.
time() should never be called here, either explicitly, or implictly by not giving the date function a second argument. Instead, utctime_to_usertime() should be used to get the timestamp.
Any times that go-in-to or come-in-from the calendar backend API are also in "user time".
To complicate matters further "user time" is not the timestamp that would exist on a user's PC, as all timestamps are meant to be stored in GMT. "user time" is offsetted to compensate, a virtual construct.
*/

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

    /**
     * Uninstall the module.
     */
    public function uninstall()
    {
        $GLOBALS['SITE_DB']->drop_table_if_exists('calendar_events');
        $GLOBALS['SITE_DB']->drop_table_if_exists('calendar_types');
        $GLOBALS['SITE_DB']->drop_table_if_exists('calendar_reminders');
        $GLOBALS['SITE_DB']->drop_table_if_exists('calendar_interests');
        $GLOBALS['SITE_DB']->drop_table_if_exists('calendar_jobs');

        delete_privilege('set_reminders');
        delete_privilege('view_event_subscriptions');
        delete_privilege('view_calendar');
        delete_privilege('add_public_events');
        delete_privilege('edit_viewable_events');
        delete_privilege('edit_owned_events');
        delete_privilege('sense_personal_conflicts');
        delete_privilege('calendar_add_to_others');

        delete_privilege('autocomplete_keyword_event');
        delete_privilege('autocomplete_title_event');

        $GLOBALS['SITE_DB']->query_delete('group_category_access', array('module_the_name' => 'calendar'));
    }

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

        if (is_null($upgrade_from)) {
            add_privilege('CALENDAR', 'view_calendar', true);
            add_privilege('CALENDAR', 'add_public_events', true);
            add_privilege('CALENDAR', 'sense_personal_conflicts', false);
            add_privilege('CALENDAR', 'view_event_subscriptions', false);

            $GLOBALS['SITE_DB']->create_table('calendar_events', array(
                'id' => '*AUTO',
                'e_submitter' => 'MEMBER',
                'e_member_calendar' => '?MEMBER', // Which member's calendar it shows on; if null, it shows globally
                'e_views' => 'INTEGER',
                'e_title' => 'SHORT_TRANS__COMCODE',
                'e_content' => 'LONG_TRANS__COMCODE',
                'e_add_date' => 'TIME',
                'e_edit_date' => '?TIME',
                'e_recurrence' => 'ID_TEXT', // [none, daily, weekly, monthly, yearly, xth_dotw_of_monthly] X [fractional-recurrence]. e.g. "daily yyyyynn" for weekdays
                'e_recurrences' => '?SHORT_INTEGER', // null means none/infinite
                'e_seg_recurrences' => 'BINARY',
                'e_start_year' => 'INTEGER',
                'e_start_month' => 'SHORT_INTEGER',
                'e_start_day' => 'SHORT_INTEGER',
                'e_start_monthly_spec_type' => 'ID_TEXT', // day_of_month|day_of_month_backwards|dow_of_month|dow_of_month_backwards
                'e_start_hour' => '?SHORT_INTEGER',
                'e_start_minute' => '?SHORT_INTEGER',
                'e_end_year' => '?INTEGER',
                'e_end_month' => '?SHORT_INTEGER',
                'e_end_day' => '?SHORT_INTEGER',
                'e_end_monthly_spec_type' => 'ID_TEXT', // day_of_month|day_of_month_backwards|dow_of_month|dow_of_month_backwards
                'e_end_hour' => '?SHORT_INTEGER',
                'e_end_minute' => '?SHORT_INTEGER',
                'e_timezone' => 'ID_TEXT', // The settings above are stored in GMT, were converted from this timezone, and back to this timezone if e_do_timezone_conv==1
                'e_do_timezone_conv' => 'BINARY',
                'e_priority' => 'SHORT_INTEGER', // 1-5, like e-mail
                'allow_rating' => 'BINARY',
                'allow_comments' => 'SHORT_INTEGER',
                'allow_trackbacks' => 'BINARY',
                'notes' => 'LONG_TEXT',
                'e_type' => 'AUTO_LINK',
                'validated' => 'BINARY',
            ));
            $GLOBALS['SITE_DB']->create_index('calendar_events', 'e_views', array('e_views'));
            $GLOBALS['SITE_DB']->create_index('calendar_events', 'ces', array('e_submitter'));
            $GLOBALS['SITE_DB']->create_index('calendar_events', 'e_type', array('e_type'));
            $GLOBALS['SITE_DB']->create_index('calendar_events', 'eventat', array('e_start_year', 'e_start_month', 'e_start_day', 'e_start_hour', 'e_start_minute'));
            $GLOBALS['SITE_DB']->create_index('calendar_events', 'e_add_date', array('e_add_date'));
            $GLOBALS['SITE_DB']->create_index('calendar_events', 'validated', array('validated'));
            $GLOBALS['SITE_DB']->create_index('calendar_events', 'ftjoin_etitle', array('e_title'));
            $GLOBALS['SITE_DB']->create_index('calendar_events', 'ftjoin_econtent', array('e_content'));

            $GLOBALS['SITE_DB']->create_table('calendar_types', array(
                'id' => '*AUTO',
                't_title' => 'SHORT_TRANS__COMCODE',
                't_logo' => 'SHORT_TEXT',
                't_external_feed' => 'URLPATH',
            ));
            $default_types = array('system_command', 'general', 'birthday', 'public_holiday', 'vacation', 'appointment', 'commitment', 'anniversary');
            require_code('lang3');
            foreach ($default_types as $type) {
                $map = array(
                    't_external_feed' => '',
                    't_logo' => 'calendar/' . $type,
                );
                $map += lang_code_to_default_content('t_title', 'DEFAULT_CALENDAR_TYPE__' . $type, true);
                $GLOBALS['SITE_DB']->query_insert('calendar_types', $map);
            }

            $GLOBALS['SITE_DB']->create_table('calendar_reminders', array(
                'id' => '*AUTO',
                'e_id' => 'AUTO_LINK',
                'n_member_id' => 'MEMBER',
                'n_seconds_before' => 'INTEGER'
            ));

            $GLOBALS['SITE_DB']->create_table('calendar_interests', array(
                'i_member_id' => '*MEMBER',
                't_type' => '*AUTO_LINK'
            ));

            $GLOBALS['SITE_DB']->create_table('calendar_jobs', array(
                'id' => '*AUTO',
                'j_time' => 'TIME',
                'j_reminder_id' => '?AUTO_LINK',
                'j_member_id' => '?MEMBER',
                'j_event_id' => 'AUTO_LINK'
            ));
            $GLOBALS['SITE_DB']->create_index('calendar_jobs', 'applicablejobs', array('j_time'));
        }

        if ((!is_null($upgrade_from)) && ($upgrade_from < 6)) {
            $GLOBALS['SITE_DB']->delete_table_field('calendar_events', 'e_geo_position');
            $GLOBALS['SITE_DB']->delete_table_field('calendar_events', 'e_groups_access');
            $GLOBALS['SITE_DB']->delete_table_field('calendar_events', 'e_groups_modify');
            $GLOBALS['SITE_DB']->add_table_field('calendar_events', 'e_timezone', 'ID_TEXT');
            $GLOBALS['SITE_DB']->add_table_field('calendar_events', 'e_do_timezone_conv', 'BINARY');
            $GLOBALS['SITE_DB']->alter_table_field('calendar_events', 'e_start_hour', '?INTEGER');
            $GLOBALS['SITE_DB']->alter_table_field('calendar_events', 'e_start_minute', '?INTEGER');

            $GLOBALS['SITE_DB']->add_table_field('calendar_types', 't_external_feed', 'URLPATH');
        }

        if ((is_null($upgrade_from)) || ($upgrade_from < 6)) {
            // Save in permissions for event type
            $types = $GLOBALS['SITE_DB']->query_select('calendar_types');
            foreach ($types as $type) {
                if ($type['id'] != db_get_first_id()) {
                    require_code('permissions2');
                    set_global_category_access('calendar', $type['id']);
                }
            }
        }

        if ((!is_null($upgrade_from)) && ($upgrade_from < 7)) {
            $GLOBALS['SITE_DB']->add_table_field('calendar_events', 'e_start_monthly_spec_type', 'ID_TEXT', 'day_of_month');
            $GLOBALS['SITE_DB']->add_table_field('calendar_events', 'e_end_monthly_spec_type', 'ID_TEXT', 'day_of_month');
        }

        if ((!is_null($upgrade_from)) && ($upgrade_from < 7)) {
            add_privilege('CALENDAR', 'set_reminders', false);
        }

        if ((is_null($upgrade_from)) || ($upgrade_from < 8)) {
            add_privilege('CALENDAR', 'calendar_add_to_others', true);

            $GLOBALS['SITE_DB']->create_index('calendar_events', '#event_search__combined', array('e_title', 'e_content'));

            add_privilege('SEARCH', 'autocomplete_keyword_event', false);
            add_privilege('SEARCH', 'autocomplete_title_event', false);
        }

        if ((!is_null($upgrade_from)) && ($upgrade_from < 8)) {
            $GLOBALS['SITE_DB']->add_table_field('calendar_events', 'e_member_calendar', '?MEMBER');
            if (addon_installed('content_privacy')) {
                $private_events = $GLOBALS['SITE_DB']->query_select('calendar_events', array('id'), array('e_is_public' => 0));
                foreach ($private_events as $private_event) {
                    $GLOBALS['SITE_DB']->query_insert('content_privacy', array(
                        'content_type' => 'event',
                        'content_id' => strval($private_event['id']),
                        'guest_view' => 0,
                        'member_view' => 0,
                        'friend_view' => 0,
                    ));
                }
            }
            $GLOBALS['SITE_DB']->delete_table_field('calendar_events', 'e_is_public');

            delete_privilege('view_personal_events');
        }
    }

    /**
     * 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)
    {
        return array(
            'browse' => array('CALENDAR', 'menu/rich_content/calendar'),
        );
    }

    public $title;
    public $id;
    public $event;
    public $title_to_use;
    public $title_to_use_2;
    public $_is_public;
    public $date;
    public $_title;
    public $first_date;
    public $back_url;

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

        require_lang('calendar');
        require_code('calendar');

        set_feed_url('?mode=calendar&select=' . urlencode(implode(',', $this->get_and_filter())));

        inform_non_canonical_parameter('#^int_.*$#');
        inform_non_canonical_parameter('member_id');

        if ($type == 'view') {
            $id = get_param_integer('id');

            $filter = $this->get_filter();

            // Read row
            $rows = $GLOBALS['SITE_DB']->query_select('calendar_events e LEFT JOIN ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'calendar_types t ON t.id=e.e_type', array('e.*', 't.t_title', 't.t_logo'), array('e.id' => $id), '', 1);
            if (!array_key_exists(0, $rows)) {
                warn_exit(do_lang_tempcode('MISSING_RESOURCE', 'event'));
            }
            $event = $rows[0];

            // Set SEO ignores
            if ($event['e_seg_recurrences'] == 0) {
                inform_non_canonical_parameter('day');
                inform_non_canonical_parameter('date');
            }
            inform_non_canonical_parameter('back');

            // Check permissions
            check_privilege('view_calendar');
            if ($event['e_member_calendar'] !== get_member()) {
                if (addon_installed('content_privacy')) {
                    require_code('content_privacy');
                    check_privacy('event', strval($id));
                }
            }
            if (!has_category_access(get_member(), 'calendar', strval($event['e_type']))) {
                access_denied('CATEGORY_ACCESS');
            }

            // Privacy
            $_is_public = true;
            if (addon_installed('content_privacy')) {
                require_code('content_privacy');
                if (!has_privacy_access('event', strval($id), $GLOBALS['FORUM_DRIVER']->get_guest_id())) {
                    $_is_public = false;
                }
            }

            // Title and metadata
            if ((get_value('no_awards_in_titles') !== '1') && (addon_installed('awards'))) {
                require_code('awards');
                $awards = find_awards_for('event', strval($id));
            } else {
                $awards = array();
            }
            $_title = get_translated_text($event['e_title']);
            $private = get_param_integer('private', null);
            if ($private !== 1) {
                $title_to_use = do_lang_tempcode('CALENDAR_EVENT_VCAL', make_fractionable_editable('event', $id, $_title));
            } else {
                $username = $GLOBALS['FORUM_DRIVER']->get_username(/*is_null($event['e_member_calendar']) ? $event['e_submitter'] : $event['e_member_calendar']*/$event['e_submitter'], true);
                $title_to_use = do_lang_tempcode('_CALENDAR_EVENT_VCAL', escape_html($username), make_fractionable_editable('event', $id, $_title));
            }
            $title_to_use_2 = do_lang('CALENDAR_EVENT', $_title);
            $this->title = get_screen_title($title_to_use, false, null, null, $awards);
            seo_meta_load_for('event', strval($id), $title_to_use_2);

            set_extra_request_metadata(array(
                'identifier' => '_SEARCH:calendar:view:' . strval($id),
            ), $event, 'event', strval($id));

            set_feed_url(find_script('backend') . '?mode=calendar&select=' . urlencode(implode(',', $this->get_and_filter())));

            // Back URL / breadcrumbs
            list(, $_first_date) = find_event_start_timestamp($event); // Will be first recurrence as we have not called adjust_event_dates_for_a_recurrence yet
            $first_date = date('Y-m-d', $_first_date);
            $date = get_param_string('date', $first_date); // It's year 10,000 compliant when it comes to year display ;).
            $back_type = get_param_string('back', 'day');
            $back_map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => $back_type, 'id' => $date));
            $back_url = build_url($back_map, '_SELF');
            breadcrumb_set_parents(array(array(build_page_link($back_map, '_SELF'), do_lang_tempcode('CALENDAR'))));

            $this->id = $id;
            $this->event = $event;
            $this->title_to_use = $title_to_use;
            $this->title_to_use_2 = $title_to_use_2;
            $this->_is_public = $_is_public;
            $this->date = $date;
            $this->_title = $_title;
            $this->first_date = $first_date;
            $this->back_url = $back_url;
        }

        if ($type != 'browse' && $type != 'view') {
            breadcrumb_set_parents(array(array('_SELF:_SELF:browse', do_lang_tempcode('CALENDAR'))));
        }

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

        return null;
    }

    /**
     * Execute the module.
     *
     * @return Tempcode The result of execution.
     */
    public function run()
    {
        require_lang('dates');
        require_css('calendar');
        require_code('feedback');

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

        // Decide what to do
        if ($type == 'declare_interest') {
            return $this->declare_interest();
        }
        if ($type == 'undeclare_interest') {
            return $this->undeclare_interest();
        }
        if ($type == 'unsubscribe_event') {
            return $this->unsubscribe_event();
        }
        if ($type == 'interests') {
            return $this->interests();
        }
        if ($type == 'subscribe_event') {
            return $this->subscribe_event();
        }
        if ($type == '_subscribe_event') {
            return $this->_subscribe_event();
        }
        if ($type == 'browse') {
            return $this->view_calendar();
        }
        if ($type == 'view') {
            return $this->view_event();
        }

        return new Tempcode();
    }

    /**
     * Gets the event filter, if there is one.
     *
     * @param  boolean $only_event_types Whether to only show event types
     * @return array The filter
     */
    public function get_filter($only_event_types = false)
    {
        $filter = array();
        static $types = null;
        if ($types === null) {
            $types = list_to_map('id', $GLOBALS['SITE_DB']->query_select('calendar_types', array('*')));
        }
        $types_has = array();
        foreach ($types as $type) {
            $t = $type['id'];
            $filter['int_' . strval($t)] = get_param_integer('int_' . strval($t), 0);

            if (post_param_integer('id', null) === $type['id']) {
                $filter['int_' . strval($t)] = 1;
            }
            if ($filter['int_' . strval($t)] == 1) {
                $types_has[] = $t;
            }
        }
        if (count($types_has) == 0) {
            $filter = array();
        }
        elseif (count($types_has) == 1) { // Viewing a single calendar type
            // Metadata
            set_extra_request_metadata(array(
                'identifier' => '_SEARCH:calendar:browse:int_' . strval($types_has[0]) . '=1',
            ), $types[$types_has[0]], 'calendar_type', strval($types_has[0]));
        }

        if (!$only_event_types) {
            $private_events = get_param_integer('private', null);
            if ($private_events !== null) {
                $filter['private'] = $private_events;
            }

            $member_id = get_param_integer('member_id', null);
            if ($member_id !== null) {
                $filter['member_id'] = $member_id;
            }
        }

        return $filter;
    }

    /**
     * Gets the event filter in a simple list form.
     *
     * @param  boolean $only_event_types Whether to only show event types
     * @return array The filter
     */
    public function get_and_filter($only_event_types = false)
    {
        $and_filter = array();
        $filter = $this->get_filter($only_event_types);
        foreach ($filter as $key => $val) {
            if ($val == 1) {
                $and_filter[] = intval(substr($key, 4));
            }
        }
        return $and_filter;
    }

    /**
     * View the main calendar screen, with certain filter allowances.
     *
     * @return Tempcode The UI
     */
    public function view_calendar()
    {
        check_privilege('view_calendar');

        $member_id = get_param_integer('member_id', get_member());
        $username = $GLOBALS['FORUM_DRIVER']->get_username($member_id, true);
        if (is_null($username)) {
            warn_exit(do_lang_tempcode('MEMBER_NO_EXIST'));
        }

        $view = get_param_string('view', 'day');
        $filter = $this->get_filter();
        //if ($member_id!=get_member()) enforce_personal_access($member_id); has particular filtering
        $back_url = null;

        $private = get_param_integer('private', null);

        switch ($view) {
            case 'day': // Like a diary
                $id = get_param_string('id', date('Y-m-d', utctime_to_usertime()));
                if (strpos($id, '-') === false) {
                    $id = date('Y-m-d', utctime_to_usertime()); // The ID was actually a filter, will need to use default date/time
                }
                $self_encompassing = ($id == date('Y-m-d', utctime_to_usertime()));
                $date = $id;
                $explode = explode('-', $id);
                if (count($explode) != 3) {
                    warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
                }
                $main = $this->view_calendar_view_day($id, $date, $explode, $member_id, $filter);
                $timestamp = mktime(0, 0, 0, intval($explode[1]), intval($explode[2]), intval($explode[0]));
                $back = get_week_number_for(utctime_to_usertime($timestamp));
                $back_view = 'week';
                $previous_timestamp = mktime(0, 0, 0, intval($explode[1]), intval($explode[2]), intval($explode[0])) - 60 * 60 * 24;
                $previous = date('Y-m-d', $previous_timestamp);
                $next_timestamp = mktime(0, 0, 0, intval($explode[1]), intval($explode[2]), intval($explode[0])) + 60 * 60 * 24;
                $next = date('Y-m-d', $next_timestamp);

                $title_date = cms_strftime(do_lang('calendar_date_verbose'), $timestamp);
                if ($private !== 1) {
                    $this->title = get_screen_title('CALENDAR_SPECIFIC', true, array(escape_html($title_date)));
                } else {
                    $this->title = get_screen_title('_CALENDAR_SPECIFIC', true, array(escape_html($username), escape_html($title_date)));
                }

                break;
            case 'week': // Like a compressed diary
                $id = get_param_string('id', get_week_number_for(utctime_to_usertime()));
                $self_encompassing = ($id == get_week_number_for(utctime_to_usertime()));
                $explode = explode('-', $id);
                if (count($explode) != 2) {
                    warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
                }
                list($start_month, $start_day, $start_year) = date_from_week_of_year(intval($explode[0]), intval($explode[1]));
                $date = strval($start_year) . '-' . strval($start_month) . '-01';
                $main = $this->view_calendar_view_week($id, $date, $explode, $member_id, $filter);
                $timestamp = mktime(0, 0, 0, $start_month, $start_day, $start_year);
                $back = $explode[0] . '-' . strval($start_month);
                $back_view = 'month';
                $previous_timestamp = mktime(0, 0, 0, $start_month, $start_day, $start_year) - 60 * 60 * 24 * 7;
                $previous = get_week_number_for($previous_timestamp);
                $next_timestamp = mktime(0, 0, 0, $start_month, $start_day, $start_year) + 60 * 60 * 24 * 7;
                $next = get_week_number_for($next_timestamp);

                if ($private !== 1) {
                    $this->title = get_screen_title('CALENDAR_SPECIFIC_WEEK', true, array(escape_html($explode[0]), escape_html($explode[1]), escape_html(cms_strftime('%b', $timestamp))));
                } else {
                    $this->title = get_screen_title('_CALENDAR_SPECIFIC_WEEK', true, array(escape_html($username), escape_html($explode[0]), escape_html($explode[1]), escape_html(cms_strftime('%b', $timestamp))));
                }

                break;
            case 'month': // Like a main calendar page
                $id = get_param_string('id', date('Y-m', utctime_to_usertime()));
                $self_encompassing = ($id == date('Y-m', utctime_to_usertime()));
                $date = $id . '-01';
                $explode = explode('-', $id);
                if (count($explode) != 2) {
                    warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
                }
                $main = $this->view_calendar_view_month($id, $date, $explode, $member_id, $filter);
                $timestamp = mktime(0, 0, 0, intval($explode[1]), 1, intval($explode[0]));
                $back = $explode[0];
                $back_view = 'year';
                $previous_month = intval($explode[1]) - 1;
                $previous_year = intval($explode[0]);
                if ($previous_month == 0) {
                    $previous_month = 12;
                    $previous_year = $previous_year - 1;
                }
                $next_month = intval($explode[1]) + 1;
                $next_year = intval($explode[0]);
                if ($next_month == 13) {
                    $next_month = 1;
                    $next_year = $next_year + 1;
                }
                $previous_timestamp = mktime(0, 0, 0, $previous_month, 1, $previous_year);
                $previous = date('Y-m', $previous_timestamp);
                $next_timestamp = mktime(0, 0, 0, $next_month, 1, $next_year);
                $next = date('Y-m', $next_timestamp);

                $title_date = cms_strftime(do_lang('calendar_month_in_year_verbose'), $timestamp);
                if ($private !== 1) {
                    $this->title = get_screen_title('CALENDAR_SPECIFIC', true, array(escape_html($title_date)));
                } else {
                    $this->title = get_screen_title('_CALENDAR_SPECIFIC', true, array(escape_html($username), escape_html($title_date)));
                }

                break;
            case 'year': // Like front of a calendar
                $id = get_param_string('id', date('Y'));
                $self_encompassing = ($id == date('Y'));
                $date = $id . '-01-01';
                $explode = explode('-', $id);
                if (count($explode) != 1) {
                    warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
                }
                $main = $this->view_calendar_view_year($id, $date, $explode, $member_id, $filter);
                $timestamp = mktime(0, 0, 0, 1, 1, intval($id));
                $back_url = $GLOBALS['FORUM_DRIVER']->member_profile_url($member_id);
                $previous_timestamp = mktime(0, 0, 0, 1, 1, intval($explode[0]) - 1);
                $previous = date('Y', $previous_timestamp);
                $next_timestamp = mktime(0, 0, 0, 1, 1, intval($explode[0]) + 1);
                $next = date('Y', $next_timestamp);

                if ($private !== 1) {
                    $this->title = get_screen_title('CALENDAR_SPECIFIC', true, array(escape_html($id)));
                } else {
                    $this->title = get_screen_title('_CALENDAR_SPECIFIC', true, array(escape_html($username), escape_html($id)));
                }

                break;
            default:
                warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
        }

        // Nofollow stuff
        $previous_no_follow = ($previous_timestamp < time() - 60 * 60 * 24 * 31);
        $test = $GLOBALS['SITE_DB']->query_value_if_there('SELECT id FROM ' . get_table_prefix() . 'calendar_events WHERE e_start_year=' . date('Y', $next_timestamp) . ' AND e_start_month<=' . date('m', $next_timestamp) . ' OR e_start_year<' . date('Y', $next_timestamp));
        if (!is_null($test)) { // if there really are events before, this takes priority
            $previous_no_follow = false;
        }
        $next_no_follow = ($next_timestamp > time() + 60 * 60 * 24 * 31 * 6/*So can see 6 months of recurrences/empty space*/);
        $test = $GLOBALS['SITE_DB']->query_value_if_there('SELECT id FROM ' . get_table_prefix() . 'calendar_events WHERE e_start_year=' . date('Y', $next_timestamp) . ' AND e_start_month>=' . date('m', $next_timestamp) . ' OR e_start_year>' . date('Y', $next_timestamp));
        if (!is_null($test)) { // if there really are events after, this takes priority
            $next_no_follow = false;
        }
        if (/*get_bot_type()!==null Actually we can't rely on bot detection, so let's just tie to guest && */is_guest()) {
            // Some bots ignore nofollow, so let's be more forceful
            $past_no_follow = ($timestamp < time() - 60 * 60 * 24 * 31);
            $test = $GLOBALS['SITE_DB']->query_value_if_there('SELECT id FROM ' . get_table_prefix() . 'calendar_events WHERE e_start_year=' . date('Y', $timestamp) . ' AND e_start_month<=' . date('m', $timestamp) . ' OR e_start_year<' . date('Y', $timestamp));
            if (!is_null($test)) { // if there really are events before, this takes priority
                $past_no_follow = false;
            }
            $future_no_follow = ($timestamp > time() + 60 * 60 * 24 * 31 * 6/*So can see 6 months of recurrences/empty space*/);
            $test = $GLOBALS['SITE_DB']->query_value_if_there('SELECT id FROM ' . get_table_prefix() . 'calendar_events WHERE e_start_year=' . date('Y', $timestamp) . ' AND e_start_month>=' . date('m', $timestamp) . ' OR e_start_year>' . date('Y', $timestamp));
            if (!is_null($test)) { // if there really are events after, this takes priority
                $future_no_follow = false;
            }
            if ($past_no_follow || $future_no_follow) {
                attach_to_screen_header('<meta name="robots" content="noindex" />'); // XHTMLXHTML
                set_http_status_code('401');
                access_denied('NOT_AS_GUEST_CALENDAR_PERFORMANCE');
            }
        }

        $map = array_merge($filter, array('page' => '_SELF', 'view' => $view, 'id' => $previous));
        $previous_url = build_url($map, '_SELF');
        $map = array_merge($filter, array('page' => '_SELF', 'view' => $view, 'id' => $next));
        $next_url = build_url($map, '_SELF');
        if (is_null($back_url)) {
            $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => $back_view, 'id' => $back));
            $back_url = build_url($map, '_SELF');
        }

        $interests_url = build_url(array('page' => '_SELF', 'type' => 'interests', 'view' => $view, 'id' => $id), '_SELF');
        $event_types_1 = new Tempcode();
        $types = $GLOBALS['SITE_DB']->query_select('calendar_types', array('id', 't_title'), array(), 'ORDER BY ' . $GLOBALS['SITE_DB']->translate_field_ref('t_title'));
        $member_interests = collapse_1d_complexity('t_type', $GLOBALS['SITE_DB']->query_select('calendar_interests', array('t_type'), array('i_member_id' => get_member())));
        foreach ($types as $type) {
            if ($type['id'] == db_get_first_id()) {
                continue;
            }
            if (!has_category_access(get_member(), 'calendar', strval($type['id']))) {
                continue;
            }

            if (is_guest()) {
                $interested = '';
            } else {
                $test = in_array($type['id'], $member_interests);
                $interested = (!is_null($test)) ? 'not_interested' : 'interested';
            }
            $event_types_1->attach(do_template('CALENDAR_EVENT_TYPE', array('_GUID' => '104b723d5211f400267345f616c4a677', 'S' => 'I', 'INTERESTED' => $interested, 'TYPE' => get_translated_text($type['t_title']), 'TYPE_ID' => strval($type['id']))));
        }
        $filter_url = build_url(array('page' => '_SELF', 'type' => 'browse', 'view' => $view, 'id' => $id), '_SELF', null, false, true);
        $event_types_2 = new Tempcode();
        foreach ($types as $type) {
            if ($type['id'] == db_get_first_id()) {
                continue;
            }
            if (!has_category_access(get_member(), 'calendar', strval($type['id']))) {
                continue;
            }

            $interested = ((!isset($filter['int_' . strval($type['id'])])) || ($filter['int_' . strval($type['id'])] == 1)) ? 'not_interested' : 'interested';
            $event_types_2->attach(do_template('CALENDAR_EVENT_TYPE', array('_GUID' => '7511d60148835b7f4fea68a246af424e', 'S' => 'F', 'INTERESTED' => $interested, 'TYPE' => get_translated_text($type['t_title']), 'TYPE_ID' => strval($type['id']))));
        }

        $add_url = new Tempcode();
        if ((has_actual_page_access(null, 'cms_calendar', null, null)) && (has_submit_permission('low', get_member(), get_ip_address(), 'cms_calendar'))) {
            $and_filter = $this->get_and_filter(true);
            if ((has_privilege(get_member(), 'calendar_add_to_others')) || (is_null(get_param_integer('member_id', null)))) {
                $add_url = build_url(array('page' => 'cms_calendar', 'type' => 'add', 'date' => $self_encompassing ? null : $date, 'e_type' => (count($and_filter) == 1) ? $and_filter[0] : null, 'private' => get_param_integer('private', null), 'member_id' => get_param_integer('member_id', null)), get_module_zone('cms_calendar'));
            }
        }

        // Allow jumping between views
        if ($self_encompassing) {
            $timestamp = time();
        }
        $day = date('Y-m-d', $timestamp);
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $day));
        $day_url = ($view == 'day') ? new Tempcode() : build_url($map, '_SELF');
        $week = get_week_number_for($timestamp);
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'week', 'id' => $week));
        $week_url = ($view == 'week') ? new Tempcode() : build_url($map, '_SELF');
        $month = date('Y-m', $timestamp);
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'month', 'id' => $month));
        $month_url = ($view == 'month') ? new Tempcode() : build_url($map, '_SELF');
        $year = date('Y', $timestamp);
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'year', 'id' => $year));
        $year_url = ($view == 'year') ? new Tempcode() : build_url($map, '_SELF');

        // RSS
        $fields = new Tempcode();
        require_code('form_templates');
        for ($i = 0; $i < 10; $i++) {
            $fields->attach(form_input_line(do_lang_tempcode('FEED', escape_html(integer_format($i + 1))), '', 'feed_' . strval($i), cms_admirecookie('feed_' . strval($i)), false));
        }
        $rss_form = do_template('FORM', array('_GUID' => '1756a3c6a5a105ef8b2b9d2ebc9e4e86', 'HIDDEN' => '', 'TEXT' => do_lang_tempcode('DESCRIPTION_FEEDS_TO_OVERLAY'), 'URL' => get_self_url(), 'FIELDS' => $fields, 'SUBMIT_ICON' => 'buttons__proceed', 'SUBMIT_NAME' => do_lang_tempcode('PROCEED')));

        return do_template('CALENDAR_MAIN_SCREEN', array(
            '_GUID' => '147a58dbe05366ac37698a8cdd501d12',
            'RSS_FORM' => $rss_form,
            'PREVIOUS_NO_FOLLOW' => $previous_no_follow,
            'NEXT_NO_FOLLOW' => $next_no_follow,
            'DAY_URL' => $day_url,
            'WEEK_URL' => $week_url,
            'MONTH_URL' => $month_url,
            'YEAR_URL' => $year_url,
            'PREVIOUS_URL' => $previous_url,
            'NEXT_URL' => $next_url,
            'ADD_URL' => $add_url,
            'TITLE' => $this->title,
            'BACK_URL' => $back_url,
            'MAIN' => $main,
            'FILTER_URL' => $filter_url,
            'EVENT_TYPES_1' => $event_types_1,
            'INTERESTS_URL' => $interests_url,
            'EVENT_TYPES_2' => $event_types_2,
        ));
    }

    /**
     * The calendar area view for viewing a single day.
     *
     * @param  string $view_id The day we are viewing
     * @param  string $day The day (Y-m-d) we are viewing
     * @param  array $explode List of components of our viewed ID
     * @param  MEMBER $member_id The member ID we are viewing the calendar for
     * @param  ?array $filter The type filter (null: none)
     * @return Tempcode The UI
     */
    public function view_calendar_view_day($view_id, $day, $explode, $member_id, $filter)
    {
        $start_year = intval($explode[0]);
        $start_month = intval($explode[1]);
        $start_day = intval($explode[2]);

        $period_start = mktime(0, 0, 0, $start_month, $start_day, $start_year);

        $period_end = mktime(0, 0, 0, $start_month, $start_day + 1, $start_year);
        $happenings = calendar_matches(get_member(), $member_id, !has_privilege(get_member(), 'assume_any_member'), $period_start, $period_end, $filter, true, get_param_integer('private', null));

        sort_maps_by($happenings, 0);

        $hours = new Tempcode();
        $streams = array(array());
        foreach ($happenings as $happening) {
            list($e_id, $event, $from, $to, $real_from, $real_to, $utc_real_from) = $happening;

            $date = is_null($event['e_start_hour']) ? '' : cms_strftime(do_lang('calendar_minute'), $from);
            if (is_numeric($e_id)) {
                $map = array_merge($filter, array('page' => '_SELF', 'type' => 'view', 'id' => $event['e_id'], 'day' => date('Y-m-d', $utc_real_from), 'date' => $view_id));
                $url = build_url($map, '_SELF');
            } else {
                $url = $e_id;
            }
            $icon = $event['t_logo'];
            $from_h = intval(date('H', $from));
            if (!is_null($to)) {
                $date = date_range($real_from, $real_to, !is_null($event['e_start_hour']));
                if ($to >= $period_end - 60/*1 minute gap we use to stop stuff spanning to start of next day*/) {
                    $to_h = 24;
                } else {
                    $to_h = intval(date('H', $to));
                    if (date('i', $to) != '0') {
                        $to_h++;
                    }
                }
            } else {
                $to_h = $from_h;
            }

            // Try and find us a stream that can fit us
            $found_stream = null;
            foreach ($streams as $i => $stream) {
                for ($h = $from_h; $h <= $to_h; $h++) {
                    if (array_key_exists($h, $stream)) {
                        break;
                    }
                }
                if (($h == $to_h + 1) && (!array_key_exists($h - 1, $stream))) {
                    $found_stream = $i;
                    break;
                }
            }
            if (is_null($found_stream)) {
                $streams[] = array();
                $found_stream = count($streams) - 1;
            }

            // Fill in stream gaps as appropriate
            $_title = is_integer($event['e_title']) ? get_translated_text($event['e_title']) : $event['e_title'];
            $down = strval($to_h - $from_h);
            if (intval($down) < 3) {
                $description = new Tempcode();
            } else {
                if ((!is_string($event['e_content'])) || (isset($event['e_content__text_parsed']))) {
                    $just_event_row = db_map_restrict($event, array('id', 'e_content'));
                    $description = get_translated_tempcode('calendar_events', $just_event_row, 'e_content');
                } else {
                    $description = $event['e_content'];
                }
            }
            $priority_lang = do_lang_tempcode('PRIORITY_' . strval($event['e_priority']));
            $priority_icon = 'calendar/priority_' . strval($event['e_priority']);
            $streams[$found_stream][$from_h] = array('TPL' => 'CALENDAR_DAY_ENTRY', 'DESCRIPTION' => $description, 'DOWN' => $down, 'ID' => is_string($event['e_id']) ? $event['e_id'] : strval($event['e_id']), 'T_TITLE' => array_key_exists('t_title', $event) ? (is_string($event['t_title']) ? $event['t_title'] : get_translated_text($event['t_title'])) : 'RSS', 'PRIORITY' => strval($event['e_priority']), 'ICON' => $icon, 'TIME' => $date, 'TITLE' => $_title, 'URL' => $url, 'PRIORITY_LANG' => $priority_lang, 'PRIORITY_ICON' => $priority_icon, 'RECURRING' => $event['e_recurrence'] != 'none', 'VALIDATED' => $event['validated'] == 1);
            for ($h = $from_h + 1; $h < $to_h; $h++) {
                $streams[$found_stream][$h] = array('TPL' => '-1');
            }
        }

        for ($i = 0; $i < 24; $i++) {
            // Work out the hour
            $hour = cms_strftime(do_lang('date_regular_time'), $i * 60 * 60);

            // The streams need rendering for each hour
            $_streams = new Tempcode();
            foreach ($streams as $stream) {
                $down = '1';
                $priority = 'free_time';
                $entry = mixed();

                if (!array_key_exists($i, $stream)) {
                    $class = 'free_time_hourly';
                    $text = '&nbsp;';
                    $entry = static_evaluate_tempcode(do_template('CALENDAR_DAY_ENTRY_FREE', array('_GUID' => '0091fbb877164ac797cb88b4571b5d35', 'CLASS' => $class, 'TEXT' => $text))); /*XHTMLXHTML*/
                } else {
                    if ($stream[$i]['TPL'] != '-1') {
                        $down = $stream[$i]['DOWN'];
                        $priority = $stream[$i]['PRIORITY'];
                        $entry = static_evaluate_tempcode(do_template($stream[$i]['TPL'], $stream[$i])); /*XHTMLXHTML*/
                    } else {
                        $entry = '';
                    }
                }

                if ($down == '0') {
                    $down = '1';
                }
                if ($entry != '') {
                    $timestamp = $period_start + $i * 60 * 60;
                    $add_url = new Tempcode();
                    if ((has_actual_page_access(null, 'cms_calendar', null, null)) && (has_submit_permission('low', get_member(), get_ip_address(), 'cms_calendar'))) {
                        $and_filter = $this->get_and_filter(true);
                        if ((has_privilege(get_member(), 'calendar_add_to_others')) || (is_null(get_param_integer('member_id', null)))) {
                            $add_url = build_url(array('page' => 'cms_calendar', 'type' => 'add', 'date' => date('Y-m-d H:i:s', $timestamp), 'e_type' => (count($and_filter) == 1) ? $and_filter[0] : null, 'private' => get_param_integer('private', null), 'member_id' => get_param_integer('member_id', null)), get_module_zone('cms_calendar'));
                        }
                    }
                    $_streams->attach(/*XHTMLXHTML*/static_evaluate_tempcode(do_template('CALENDAR_DAY_STREAM_HOUR', array('_GUID' => '93a8fb53183a4225ec3bf7f2ea07cfc5', 'CURRENT' => date('Y-m-d H', utctime_to_usertime()) == date('Y-m-d H', $timestamp), 'ADD_URL' => $add_url, 'PRIORITY' => $priority, 'DOWN' => $down, 'ENTRY' => $entry))));
                }
            }

            $hours->attach(do_template('CALENDAR_DAY_HOUR', array('_GUID' => 'd967ce3f793942f78104c53b105f9f74', '_HOUR' => strval($i), 'HOUR' => $hour, 'STREAMS' => $_streams)));
        }

        return do_template('CALENDAR_DAY', array('_GUID' => '60e102b38025c1b1618ac36070564065', 'HOURS' => $hours, 'PERIOD_START' => strval($period_start), 'PERIOD_END' => strval($period_end)));
    }

    /**
     * The calendar area view for viewing a single week.
     *
     * @param  string $view_id The week we are viewing
     * @param  string $day The day (Y-m-d) we are viewing
     * @param  array $explode List of components of our viewed ID
     * @param  MEMBER $member_id The member ID we are viewing the calendar for
     * @param  ?array $filter The type filter (null: none)
     * @return Tempcode The UI
     */
    public function view_calendar_view_week($view_id, $day, $explode, $member_id, $filter)
    {
        $start_year = intval($explode[0]);
        $start_week = intval($explode[1]);
        list($start_month, $start_day, $start_year) = date_from_week_of_year($start_year, $start_week);

        $period_start = mktime(0, 0, 0, $start_month, $start_day, $start_year);
        $period_end = $period_start + 60 * 60 * 24 * 7;
        $happenings = calendar_matches(get_member(), $member_id, !has_privilege(get_member(), 'assume_any_member'), $period_start, $period_end, $filter, true, get_param_integer('private', null));

        sort_maps_by($happenings, 0);

        if (get_option('ssw') == '0') {
            $day_remap = array('Mon' => 0, 'Tue' => 1, 'Wed' => 2, 'Thu' => 3, 'Fri' => 4, 'Sat' => 5, 'Sun' => 6);
        } else {
            $day_remap = array('Sun' => 0, 'Mon' => 1, 'Tue' => 2, 'Wed' => 3, 'Thu' => 4, 'Fri' => 5, 'Sat' => 6);
        }

        // We start with our 24x7 stream array, assuming all is free time
        $streams = array();  // hour X day
        for ($i = 0; $i < 24; $i++) {
            $streams[$i] = array();
            for ($j = 0; $j < 7; $j++) {
                $streams[$i][$j] = array('free_time', '', $i);
            }
        }
        for ($j = 0; $j < 7; $j++) {
            for ($i = 0; $i < 24; $i++) {
                $entries = new Tempcode();

                $class = '';
                $worst_priority = 6;
                $continuation = $i;

                // Happenings in a single day
                $cnt = count($happenings);
                for ($hap_i = 0; $hap_i < $cnt; $hap_i++) {
                    $happening = $happenings[$hap_i];
                    list($e_id, $event, $from, $to, $real_from, $real_to, $utc_real_from) = $happening;
                    $date = date('D:H', $from);
                    $explode2 = explode(':', $date);
                    if ((intval($explode2[1]) == $i) && ($day_remap[$explode2[0]] == $j)) {
                        $date = is_null($event['e_start_hour']) ? '' : cms_strftime(do_lang('calendar_minute'), $real_from);
                        if (is_numeric($e_id)) {
                            $map = array_merge($filter, array('page' => '_SELF', 'type' => 'view', 'id' => $event['e_id'], 'day' => date('Y-m-d', $utc_real_from), 'date' => $view_id, 'back' => 'week'));
                            $url = build_url($map, '_SELF');
                        } else {
                            $url = $e_id;
                        }
                        $icon = $event['t_logo'];
                        if (!is_null($to)) {
                            $date = date_range($real_from, $real_to, !is_null($event['e_start_hour']));
                            if ((!is_null($to)) && ($to >= mktime(0, 0, 0, $start_month, $start_day + $j + 1, $start_year))) {
                                $continuation = 24;
                                $ntime = mktime(0, 0, 0, $start_month, $start_day + $j + 1, $start_year);
                                if ($ntime < $period_end) {
                                    $happenings[] = array($e_id, $event, $ntime, $to, $real_from, $real_to, $utc_real_from);
                                }
                            } elseif (intval(date('H', $to)) > $i + 1) {
                                $continuation = max($continuation, intval(date('H', $to)));
                            }
                        }
                        $_title = is_integer($event['e_title']) ? get_translated_text($event['e_title']) : $event['e_title'];
                        $entries->attach(do_template('CALENDAR_WEEK_ENTRY', array(
                            '_GUID' => 'a5577fb634ecc5480789d1cd21f686fb',
                            'ID' => is_string($event['e_id']) ? $event['e_id'] : strval($event['e_id']),
                            'T_TITLE' => array_key_exists('t_title', $event) ? (is_string($event['t_title']) ? $event['t_title'] : get_translated_text($event['t_title'])) : 'RSS',
                            'PRIORITY' => strval($event['e_priority']),
                            'ICON' => $icon,
                            'TIME' => $date,
                            'TITLE' => $_title,
                            'URL' => $url,
                            'RECURRING' => $event['e_recurrence'] != 'none',
                            'VALIDATED' => $event['validated'] == 1,
                        )));

                        if ($event['e_priority'] < $worst_priority) {
                            $worst_priority = $event['e_priority'];
                            $class = 'priority_' . strval($event['e_priority']);
                        }
                    }
                }

                // Not free time anymore
                if ($class != '') {
                    $streams[$i][$j] = array($class, $entries, $continuation);

                    // Now work backwards to cut back any continuation onto this spot
                    for ($k = $i - 1; $k >= 0; $k--) {
                        if ($streams[$k][$j][2] >= $i) {
                            $streams[$k][$j][2] = $i - 1;
                        }
                    }
                }
            }
        }

        // Now mark continuations ahead
        for ($i = 0; $i < 24; $i++) {
            for ($j = 0; $j < 7; $j++) {
                if ($streams[$i][$j][0] != 'continuation') {
                    $continuation = $streams[$i][$j][2];
                    for ($k = $i + 1; $k <= $continuation; $k++) {
                        $streams[$k][$j][0] = 'continuation';
                    }
                }
            }
        }

        // Now render
        $hours = new Tempcode();
        for ($i = 0; $i < 24; $i++) {
            $hour = cms_strftime(do_lang('date_regular_time'), $i * 60 * 60);

            $days = new Tempcode();
            for ($j = 0; $j < 7; $j++) {
                $class = $streams[$i][$j][0];
                $entries = $streams[$i][$j][1];
                $continuation = $streams[$i][$j][2];

                if (((!is_object($entries)) || ($entries->is_empty())) && ($class != 'continuation')) {
                    $entries = do_template('CALENDAR_WEEK_ENTRY_FREE', array('_GUID' => '4a16cdd5a2a0e5a444fd61a6aafa2ffa', 'CLASS' => $class, 'TEXT' => ''));
                }

                $down = $continuation - $i;

                if ($class != 'continuation') {
                    $timestamp = $period_start + ($i + 24 * $j) * 60 * 60;
                    $add_url = new Tempcode();
                    if ((has_actual_page_access(null, 'cms_calendar', null, null)) && (has_submit_permission('low', get_member(), get_ip_address(), 'cms_calendar'))) {
                        $and_filter = $this->get_and_filter(true);
                        if ((has_privilege(get_member(), 'calendar_add_to_others')) || (is_null(get_param_integer('member_id', null)))) {
                            $add_url = build_url(array('page' => 'cms_calendar', 'type' => 'add', 'date' => date('Y-m-d H:i:s', $timestamp), 'e_type' => (count($and_filter) == 1) ? $and_filter[0] : null, 'private' => get_param_integer('private', null), 'member_id' => get_param_integer('member_id', null)), get_module_zone('cms_calendar'));
                        }
                    }
                    $days->attach(do_template('CALENDAR_WEEK_HOUR_DAY', array('_GUID' => 'e001b4b2ea1995760ef0d4460d93b2e1', 'CURRENT' => date('Y-m-d', utctime_to_usertime()) == date('Y-m-d', $timestamp), 'ADD_URL' => $add_url, 'DOWN' => strval($down + 1), 'DAY' => $day, 'HOUR' => $hour, 'CLASS' => $class, 'ENTRIES' => $entries)));
                }
            }

            $hours->attach(do_template('CALENDAR_WEEK_HOUR', array('_GUID' => 'a57d0d6a683d30fc0a48168b43299607', '_HOUR' => strval($i), 'HOUR' => $hour, 'DAYS' => $days)));
        }

        $offset = 0;
        if (get_option('ssw') == '1') {
            $datex = date('Y-m-d', mktime(0, 0, 0, $start_month, $start_day + $offset, intval($explode[0])));
            $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $datex));
            $sunday_url = build_url($map, '_SELF');
            $sunday_date = cms_strftime(do_lang('calendar_day_of_month_verbose'), $period_start + $offset * 60 * 60 * 24);
            $offset++;
        }
        $datex = date('Y-m-d', mktime(0, 0, 0, $start_month, $start_day + $offset, intval($explode[0])));
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $datex));
        $monday_url = build_url($map, '_SELF');
        $monday_date = cms_strftime(do_lang('calendar_day_of_month_verbose'), $period_start + $offset * 60 * 60 * 24);
        $offset++;
        $datex = date('Y-m-d', mktime(0, 0, 0, $start_month, $start_day + $offset, intval($explode[0])));
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $datex));
        $tuesday_url = build_url($map, '_SELF');
        $tuesday_date = cms_strftime(do_lang('calendar_day_of_month_verbose'), $period_start + $offset * 60 * 60 * 24);
        $offset++;
        $datex = date('Y-m-d', mktime(0, 0, 0, $start_month, $start_day + $offset, intval($explode[0])));
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $datex));
        $wednesday_url = build_url($map, '_SELF');
        $wednesday_date = cms_strftime(do_lang('calendar_day_of_month_verbose'), $period_start + $offset * 60 * 60 * 24);
        $offset++;
        $datex = date('Y-m-d', mktime(0, 0, 0, $start_month, $start_day + $offset, intval($explode[0])));
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $datex));
        $thursday_url = build_url($map, '_SELF');
        $thursday_date = cms_strftime(do_lang('calendar_day_of_month_verbose'), $period_start + $offset * 60 * 60 * 24);
        $offset++;
        $datex = date('Y-m-d', mktime(0, 0, 0, $start_month, $start_day + $offset, intval($explode[0])));
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $datex));
        $friday_url = build_url($map, '_SELF');
        $friday_date = cms_strftime(do_lang('calendar_day_of_month_verbose'), $period_start + $offset * 60 * 60 * 24);
        $offset++;
        $datex = date('Y-m-d', mktime(0, 0, 0, $start_month, $start_day + $offset, intval($explode[0])));
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $datex));
        $saturday_url = build_url($map, '_SELF');
        $saturday_date = cms_strftime(do_lang('calendar_day_of_month_verbose'), $period_start + $offset * 60 * 60 * 24);
        $offset++;
        if (get_option('ssw') == '0') {
            $datex = date('Y-m-d', mktime(0, 0, 0, $start_month, $start_day + $offset, intval($explode[0])));
            $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $datex));
            $sunday_url = build_url($map, '_SELF');
            $sunday_date = cms_strftime(do_lang('calendar_day_of_month_verbose'), $period_start + $offset * 60 * 60 * 24);
            $offset++;
        }

        return do_template('CALENDAR_WEEK', array(
            '_GUID' => '8dd9ad4e874d2f320a48551e0c9bde57',
            'MONDAY_DATE' => $monday_date,
            'TUESDAY_DATE' => $tuesday_date,
            'WEDNESDAY_DATE' => $wednesday_date,
            'THURSDAY_DATE' => $thursday_date,
            'FRIDAY_DATE' => $friday_date,
            'SATURDAY_DATE' => $saturday_date,
            'SUNDAY_DATE' => $sunday_date,
            'MONDAY_URL' => $monday_url,
            'TUESDAY_URL' => $tuesday_url,
            'WEDNESDAY_URL' => $wednesday_url,
            'THURSDAY_URL' => $thursday_url,
            'FRIDAY_URL' => $friday_url,
            'SATURDAY_URL' => $saturday_url,
            'SUNDAY_URL' => $sunday_url,
            'HOURS' => $hours,
            'PERIOD_START' => strval($period_start),
            'PERIOD_END' => strval($period_end),
        ));
    }

    /**
     * The calendar area view for viewing a single month.
     *
     * @param  string $view_id The month we are viewing
     * @param  string $day The day (Y-m-d) we are viewing
     * @param  array $explode List of components of our viewed ID
     * @param  MEMBER $member_id The member ID we are viewing the calendar for
     * @param  ?array $filter The type filter (null: none)
     * @return Tempcode The UI
     */
    public function view_calendar_view_month($view_id, $day, $explode, $member_id, $filter)
    {
        $period_start = mktime(0, 0, 0, intval($explode[1]), 1, intval($explode[0]));
        $period_end = mktime(0, 0, 0, intval($explode[1]) + 1, 1, intval($explode[0]));
        $_days = intval(round(floatval($period_end - $period_start) / floatval(60 * 60 * 24)));
        if ($_days == 0) {
            warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
        }

        $happenings = calendar_matches(get_member(), $member_id, !has_privilege(get_member(), 'assume_any_member'), $period_start, $period_end, $filter, true, get_param_integer('private', null));

        sort_maps_by($happenings, 0);

        if (get_option('ssw') == '0') {
            $ex_array = array('Mon' => 0, 'Tue' => 1, 'Wed' => 2, 'Thu' => 3, 'Fri' => 4, 'Sat' => 5, 'Sun' => 6);
        } else {
            $ex_array = array('Sun' => 0, 'Mon' => 1, 'Tue' => 2, 'Wed' => 3, 'Thu' => 4, 'Fri' => 5, 'Sat' => 6);
        }
        $empty_entry = do_template('CALENDAR_MONTH_ENTRY_FREE', array('_GUID' => 'c8eba771bd5f9a58d4822f4a2dac57a2', 'CLASS' => 'spacer', 'TEXT' => ''));

        $weeks = new Tempcode();
        $days = new Tempcode();
        $week_date = cms_strftime(do_lang('calendar_day_of_month'), $period_start);
        $week_count = intval(get_week_number_for($period_start, true));
        $day_of_week = date('D', $period_start);
        if (!isset($ex_array[$day_of_week])) {
            warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
        }
        for ($x = 0; $x < $ex_array[$day_of_week]; $x++) {
            $days->attach(do_template('CALENDAR_MONTH_DAY', array('_GUID' => '783ff6377292cc0400638c8857446a16', 'CURRENT' => false, 'DAY_URL' => '', 'CLASS' => '', 'DAY' => '', 'ENTRIES' => $empty_entry)));
        }
        $dotw = $ex_array[$day_of_week] - 1;
        for ($i = 1; $i <= $_days; $i++) {
            $entries = new Tempcode();

            $timestamp = $period_start + ($i - 1) * 60 * 60 * 24 + 60 * 60;
            $day_of_week = date('D', $timestamp);
            if ($dotw == 6) {
                $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'week', 'id' => $explode[0] . '-' . strval($week_count)));
                $week_url = build_url($map, '_SELF');
                $weeks->attach(do_template('CALENDAR_MONTH_WEEK', array('_GUID' => '1010b12d8677bc577f30a013e9a838ce', 'WEEK_URL' => $week_url, 'WEEK_DATE' => $week_date, 'DAYS' => $days)));
                $days = new Tempcode();
                $week_date = cms_strftime(do_lang('calendar_day_of_month'), $period_start + ($i - 1) * 60 * 60 * 24);
                $week_count++;
                $dotw = 0;
            } else {
                $dotw++;
            }

            $class = '';
            $worst_priority = 6;

            $cnt = count($happenings);
            for ($hap_i = 0; $hap_i < $cnt; $hap_i++) {
                $happening = $happenings[$hap_i];

                list($e_id, $event, $from, $to, $real_from, $real_to, $utc_real_from) = $happening;
                $date = date('d', $from);
                if (intval($date) == $i) {
                    $date = is_null($event['e_start_hour']) ? '' : cms_strftime(do_lang('calendar_minute'), $real_from);

                    if (is_numeric($e_id)) {
                        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'view', 'id' => $event['e_id'], 'day' => date('Y-m-d', $utc_real_from), 'date' => $view_id, 'back' => 'month'));
                        $url = build_url($map, '_SELF');
                    } else {
                        $url = $e_id;
                    }
                    $icon = $event['t_logo'];
                    if (!is_null($to)) {
                        $date = date_range($real_from, $real_to, !is_null($event['e_start_hour']));
                    }
                    $_title = is_integer($event['e_title']) ? get_translated_text($event['e_title']) : $event['e_title'];
                    $entries->attach(do_template('CALENDAR_MONTH_ENTRY', array(
                        '_GUID' => '58353fc64595f981d41da303cfe40855',
                        'ID' => is_string($event['e_id']) ? $event['e_id'] : strval($event['e_id']),
                        'T_TITLE' => array_key_exists('t_title', $event) ? (is_string($event['t_title']) ? $event['t_title'] : get_translated_text($event['t_title'])) : 'RSS',
                        'PRIORITY' => strval($event['e_priority']),
                        'ICON' => $icon,
                        'TIME' => $date,
                        'TITLE' => $_title,
                        'URL' => $url,
                        'RECURRING' => $event['e_recurrence'] != 'none',
                        'VALIDATED' => $event['validated'] == 1,
                    )));

                    if ($event['e_priority'] < $worst_priority) {
                        $worst_priority = $event['e_priority'];
                        $class = 'priority_' . strval($event['e_priority']);
                    }

                    if (!is_null($to)) {
                        $test = date('d', $to);
                        $test2 = date('d', $from);
                        if (((intval($test) > intval($test2)) || (intval(date('m', $to)) != intval(date('m', $from))) || (intval(date('Y', $to)) != intval(date('Y', $from))))) {
                            $ntime = mktime(0, 0, 0, intval(date('m', $from)), intval($test2) + 1, intval(date('Y', $from)));
                            if ($ntime < $period_end) {
                                $happenings[] = array($e_id, $event, $ntime, $to, $real_from, $real_to, $utc_real_from);
                            }
                        }
                    }
                }
            }

            if ($entries->is_empty()) {
                $class = 'free_time';
                $text = '&nbsp;';
                $entries = do_template('CALENDAR_MONTH_ENTRY_FREE', array('_GUID' => 'a5e193c8c14deb17ac629e0de74458a2', 'CLASS' => $class, 'TEXT' => $text));
            }

            $day_url = build_url(array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $explode[0] . '-' . $explode[1] . '-' . str_pad(strval($i), 2, '0', STR_PAD_LEFT))), '_SELF');
            $days->attach(do_template('CALENDAR_MONTH_DAY', array('_GUID' => '44162c09e0647888cf079c0ac78c1912', 'CURRENT' => date('Y-m-d') == date('Y-m-d', $timestamp), 'DAY_URL' => $day_url, 'CLASS' => $class, 'DAY' => strval($i), 'ENTRIES' => $entries)));
        }
        $ex_array = array_flip(array_reverse(array_flip($ex_array)));
        for ($x = 0; $x < $ex_array[$day_of_week]; $x++) {
            $days->attach(do_template('CALENDAR_MONTH_DAY', array('_GUID' => '3a34ca72f31d9244b9eb642814836736', 'CURRENT' => false, 'DAY_URL' => '', 'CLASS' => '', 'DAY' => '', 'ENTRIES' => $empty_entry)));
        }
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'week', 'id' => $explode[0] . '-' . strval($week_count)));
        $week_url = build_url($map, '_SELF');
        $weeks->attach(do_template('CALENDAR_MONTH_WEEK', array('_GUID' => '0fc7a1691b3cb081d80519a2fe666849', 'WEEK_URL' => $week_url, 'WEEK_DATE' => $week_date, 'DAYS' => $days)));

        return do_template('CALENDAR_MONTH', array('_GUID' => '2ada774f9eb9524db1bed9bea440e5d1', 'WEEKS' => $weeks, 'PERIOD_START' => strval($period_start), 'PERIOD_END' => strval($period_end)));
    }

    /**
     * The calendar area view for viewing a single year.
     *
     * @param  string $view_id The year we are viewing
     * @param  string $day The day (Y-m-d) we are viewing
     * @param  array $explode List of components of our viewed ID
     * @param  MEMBER $member_id The member ID we are viewing the calendar for
     * @param  ?array $filter The type filter (null: none)
     * @return Tempcode The UI
     */
    public function view_calendar_view_year($view_id, $day, $explode, $member_id, $filter)
    {
        $period_start = mktime(0, 0, 0, 1, 1, intval($explode[0]));
        $period_end = mktime(0, 0, 0, 1, 0, intval($explode[0]) + 1);

        $happenings = calendar_matches(get_member(), $member_id, !has_privilege(get_member(), 'assume_any_member'), $period_start, $period_end, $filter, true, get_param_integer('private', null));

        sort_maps_by($happenings, 0);

        $months = '';
        $month_rows = new Tempcode();
        for ($i = 1; $i <= 12; $i++) {
            $entries = array();
            $priorities = array();

            if ((($i - 1) % 3 == 0) && ($i != 1)) {
                if ($i == 4) {
                    list($month_a, $month_b, $month_c) = array(do_lang_tempcode('JANUARY'), do_lang_tempcode('FEBRUARY'), do_lang_tempcode('MARCH'));
                }
                if ($i == 7) {
                    list($month_a, $month_b, $month_c) = array(do_lang_tempcode('APRIL'), do_lang_tempcode('MAY'), do_lang_tempcode('JUNE'));
                }
                if ($i == 10) {
                    list($month_a, $month_b, $month_c) = array(do_lang_tempcode('JULY'), do_lang_tempcode('AUGUST'), do_lang_tempcode('SEPTEMBER'));
                }
                $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'month', 'id' => $explode[0] . '-' . strval($i - 3)));
                $month_a_url = build_url($map, '_SELF');
                $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'month', 'id' => $explode[0] . '-' . strval($i - 2)));
                $month_b_url = build_url($map, '_SELF');
                $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'month', 'id' => $explode[0] . '-' . strval($i - 1)));
                $month_c_url = build_url($map, '_SELF');
                $month_rows->attach(do_template('CALENDAR_YEAR_MONTH_ROW', array('_GUID' => 'cf4f7b66a32afee1c69b2e4338af8dd5', 'MONTHS' => $months, 'MONTH_A_URL' => $month_a_url, 'MONTH_B_URL' => $month_b_url, 'MONTH_C_URL' => $month_c_url, 'MONTH_A' => $month_a, 'MONTH_B' => $month_b, 'MONTH_C' => $month_c)));
                $months = '';
            }

            foreach ($happenings as $happening) {
                list($e_id, $event, $from, $to, $real_from, $real_to, $utc_real_from) = $happening;
                $date = date('m', $from);
                if (intval($date) == $i) {
                    if (is_numeric($e_id)) {
                        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'view', 'id' => $event['e_id'], 'day' => date('Y-m-d', $utc_real_from), 'date' => $view_id, 'back' => 'year'));
                        $url = build_url($map, '_SELF');
                    } else {
                        $url = $e_id;
                    }
                    $icon = $event['t_logo'];
                    $_title = is_integer($event['e_title']) ? get_translated_text($event['e_title']) : $event['e_title'];
                    $date = cms_strftime(do_lang('calendar_date'), $real_from);
                    $_from = $from;
                    do {
                        $_day = intval(date('d', $_from));

                        if (!array_key_exists($_day, $entries)) {
                            $entries[$_day] = array('ID' => is_string($event['e_id']) ? $event['e_id'] : strval($event['e_id']), 'T_TITLE' => array_key_exists('t_title', $event) ? (is_string($event['t_title']) ? $event['t_title'] : get_translated_text($event['t_title'])) : 'RSS', 'PRIORITY' => strval($event['e_priority']), 'ICON' => $icon, 'TIME' => $date, 'TITLE' => $_title, 'URL' => $url);
                            $priorities[$_day] = $event['e_priority'];
                        } else { // If we have more than one, we don't store a map, we just count them
                            $entries[$_day] = (is_array($entries[$_day])) ? 2 : ($entries[$_day] + 1);
                            $priorities[$_day] = min($priorities[$_day], $event['e_priority']);
                        }

                        // Maybe spanning multiple days of month
                        if (!is_null($to)) {
                            $_explode = explode('-', date('d-m', $to));
                            $continues = (intval($_explode[0]) != $_day) || (intval($_explode[1]) != $i);
                            if ($continues) {
                                $_from = mktime(0, 0, 0, intval(date('m', $_from)), intval(date('d', $_from)) + 1, intval(date('Y', $_from)));
                                if (intval(date('m', $_from)) != $i) {
                                    $continues = false; // Don't let it roll to another month
                                }
                            }
                        } else {
                            $continues = false;
                        }
                    } while ($continues);

                    // Push to other months too
                    if (!is_null($to)) {
                        $date2 = date('m', $to);
                        if (intval($date2) > $i) {
                            $ntime = mktime(0, 0, 0, intval(date('m', $from)) + 1, 1, intval(date('Y', $from)));
                            $happenings[] = array($e_id, $event, $ntime, $to, $real_from, $real_to, $utc_real_from);
                        }
                    }
                }
            }

            $_period_start = mktime(0, 0, 0, $i, 1, intval($explode[0]));
            $_period_end = mktime(0, 0, 0, $i + 1, 0, intval($explode[0]));
            $_days = intval(round(floatval($_period_end - $_period_start) / floatval(60 * 60 * 24)));

            $_entries = new Tempcode();
            $__entries = new Tempcode();
            $_dotw = date('D', $_period_start);
            if (get_option('ssw') == '0') {
                $ex_array = array('Mon' => 0, 'Tue' => 1, 'Wed' => 2, 'Thu' => 3, 'Fri' => 4, 'Sat' => 5, 'Sun' => 6);
            } else {
                $ex_array = array('Sun' => 0, 'Mon' => 1, 'Tue' => 2, 'Wed' => 3, 'Thu' => 4, 'Fri' => 5, 'Sat' => 6);
            }
            $dotw = $ex_array[$_dotw];
            for ($j = 0; $j < $dotw; $j++) {
                $__entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_SPACER'));
            }
            for ($j = 1; $j <= $_days + 1; $j++) {
                $date = $explode[0] . '-' . str_pad(strval($i), 2, '0', STR_PAD_LEFT) . '-' . str_pad(strval($j), 2, '0', STR_PAD_LEFT);
                $date_formatted = cms_strftime(do_lang('calendar_date'), mktime(0, 0, 0, $i, $j, intval($explode[0])));
                $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'day', 'id' => $date));
                $day_url = build_url($map, '_SELF');

                if (!array_key_exists($j, $entries)) {
                    $class = 'free_time';
                    $__entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_FREE', array('_GUID' => 'cccf839895121ae14fd1948ed4024d4b', 'CURRENT' => date('Y-m-d') == $date, 'DAY_URL' => $day_url, 'DATE' => $date_formatted, 'DAY' => strval($j), 'CLASS' => $class)));
                } elseif (is_array($entries[$j])) {
                    $class = 'single';
                    $events_and_priority_lang = do_lang_tempcode('TOTAL_EVENTS_AND_HIGHEST_PRIORITY', '1', do_lang_tempcode('PRIORITY_' . strval($priorities[$j])));
                    $__entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_ACTIVE', array_merge(array('CURRENT' => date('Y-m-d') == $date, 'DAY_URL' => $day_url, 'DATE' => $date_formatted, 'DAY' => strval($j), 'CLASS' => $class, 'COUNT' => '1', 'EVENTS_AND_PRIORITY_LANG' => $events_and_priority_lang), $entries[$j])));
                } else {
                    $class = 'multiple';
                    $e_count = integer_format($entries[$j]);
                    $events_and_priority_lang = do_lang_tempcode('TOTAL_EVENTS_AND_HIGHEST_PRIORITY', make_string_tempcode($e_count), do_lang_tempcode('PRIORITY_' . strval($priorities[$j])));
                    $__entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_ACTIVE', array(
                        '_GUID' => '9f016773fce6402eca1a7a0afa6bb89f',
                        'CURRENT' => date('Y-m-d') == $date,
                        'DAY_URL' => $day_url,
                        'DATE' => $date_formatted,
                        'TITLE' => '',
                        'TIME' => '',
                        'URL' => '',
                        'ID' => '',
                        'PRIORITY' => strval($priorities[$j]),
                        'DAY' => strval($j),
                        'ICON' => '',
                        'COUNT' => $e_count,
                        'EVENTS_AND_PRIORITY_LANG' => $events_and_priority_lang,
                    )));
                }

                if ($dotw == 6) {
                    $_entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_ROW', array('_GUID' => 'f0d29f0cf53ba119a7ab4351807a9f2f', 'ENTRIES' => $__entries)));
                    $__entries = new Tempcode();
                    $dotw = 0;
                } else {
                    $dotw++;
                }
            }

            if (!$__entries->is_empty()) {
                for ($j = $dotw; $j < 7; $j++) {
                    $__entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_SPACER'));
                }
                $_entries->attach(do_template('CALENDAR_YEAR_MONTH_DAY_ROW', array('_GUID' => '5c7fa5d50e1e9eb30f0a19cc7da03c4b', 'ENTRIES' => $__entries)));
            }

            $month = do_template('CALENDAR_YEAR_MONTH', array('_GUID' => '58c9f4cc04186dce6e7ea3dd8ec9269b', 'ENTRIES' => $_entries));
            $months .= $month->evaluate(); // XHTMLXHTML
        }

        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'month', 'id' => $explode[0] . '-' . strval($i - 3)));
        $month_a_url = build_url($map, '_SELF');
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'month', 'id' => $explode[0] . '-' . strval($i - 2)));
        $month_b_url = build_url($map, '_SELF');
        $map = array_merge($filter, array('page' => '_SELF', 'type' => 'browse', 'view' => 'month', 'id' => $explode[0] . '-' . strval($i - 1)));
        $month_c_url = build_url($map, '_SELF');
        list($month_a, $month_b, $month_c) = array(do_lang_tempcode('OCTOBER'), do_lang_tempcode('NOVEMBER'), do_lang_tempcode('DECEMBER'));
        $month_rows->attach(do_template('CALENDAR_YEAR_MONTH_ROW', array('_GUID' => 'd169c06319ca2c089faa6fbb92b39115', 'MONTHS' => $months, 'MONTH_A_URL' => $month_a_url, 'MONTH_B_URL' => $month_b_url, 'MONTH_C_URL' => $month_c_url, 'MONTH_A' => $month_a, 'MONTH_B' => $month_b, 'MONTH_C' => $month_c)));

        return do_template('CALENDAR_YEAR', array('_GUID' => 'ee8f09d406eff18637b37b762bad62cd', 'MONTH_ROWS' => $month_rows, 'PERIOD_START' => strval($period_start), 'PERIOD_END' => strval($period_end)));
    }

    /**
     * View an event.
     *
     * @return Tempcode The UI
     */
    public function view_event()
    {
        $id = $this->id;
        $event = $this->event;
        $title_to_use = $this->title_to_use;
        $title_to_use_2 = $this->title_to_use_2;
        $_is_public = $this->_is_public;
        $date = $this->date;
        $_title = $this->_title;
        $first_date = $this->first_date;
        $back_url = $this->back_url;

        // Validation
        $warning_details = new Tempcode();
        if (($event['validated'] == 0) && (addon_installed('unvalidated'))) {
            if ((!has_privilege(get_member(), 'jump_to_unvalidated')) && ((is_guest()) || ($event['e_submitter'] != get_member()) && ($event['e_member_calendar'] != get_member()))) {
                access_denied('PRIVILEGE', 'jump_to_unvalidated');
            }

            $warning_details->attach(do_template('WARNING_BOX', array(
                '_GUID' => '332faacba974e648a67e5e91ffd3d8e5',
                'WARNING' => do_lang_tempcode((get_param_integer('redirected', 0) == 1) ? 'UNVALIDATED_TEXT_NON_DIRECT' : 'UNVALIDATED_TEXT', 'event'),
            )));
        }

        $just_event_row = db_map_restrict($event, array('id', 'e_content'));

        // Misc data
        $content = ($event['e_type'] == db_get_first_id()) ? make_string_tempcode(get_translated_text($event['e_content'])) : get_translated_tempcode('calendar_events', $just_event_row, 'e_content');
        $type = get_translated_text($event['t_title']);
        $priority = $event['e_priority'];
        $priority_lang = do_lang_tempcode('PRIORITY_' . strval($priority));
        $is_public = $_is_public ? do_lang_tempcode('YES') : do_lang_tempcode('NO');

        // Personal subscriptions (reminders)
        $subscribed = array();
        if ((has_privilege(get_member(), 'view_event_subscriptions')) && (cron_installed())) {
            $subscriptions = $GLOBALS['SITE_DB']->query_select('calendar_reminders', array('DISTINCT n_member_id'), array('e_id' => $id), '', 100);
            if (count($subscriptions) < 100) {
                foreach ($subscriptions as $subscription) {
                    $username = $GLOBALS['FORUM_DRIVER']->get_username($subscription['n_member_id']);
                    if (!is_null($username)) {
                        $member_url = $GLOBALS['FORUM_DRIVER']->member_profile_url($subscription['n_member_id'], false, true);
                        $subscribed[] = array('MEMBER_ID' => strval($subscription['n_member_id']), 'MEMBER_URL' => $member_url, 'USERNAME' => $username);
                    }
                }
            }
        }
        $_subscriptions = array();
        if ((is_guest()) || (!cron_installed())) {
            $subscribe_url = new Tempcode();
        } else {
            $subscriptions = $GLOBALS['SITE_DB']->query_select('calendar_reminders', array('id', 'n_seconds_before'), array('e_id' => $id, 'n_member_id' => get_member()));
            foreach ($subscriptions as $subscription) {
                $time = display_time_period(intval($subscription['n_seconds_before']));
                $unsubscribe_url = build_url(array('page' => '_SELF', 'type' => 'unsubscribe_event', 'id' => $id, 'reminder_id' => $subscription['id']), '_SELF');
                $_subscriptions[] = array('UNSUBSCRIBE_URL' => $unsubscribe_url, 'TIME' => $time);
            }
            $subscribe_url = build_url(array('page' => '_SELF', 'type' => 'subscribe_event', 'id' => $id), '_SELF');
        }

        // Feedback
        list($rating_details, $comment_details, $trackback_details) = embed_feedback_systems(
            'events',
            strval($id) . (($event['e_seg_recurrences'] == 1) ? ('_' . $date) : ''),
            $event['allow_rating'],
            $event['allow_comments'],
            $event['allow_trackbacks'],
            (get_option('is_on_strong_forum_tie') == '0') ? 1 : 0,
            $event['e_submitter'],
            build_url(array('page' => '_SELF', 'type' => 'view', 'id' => $id), '_SELF', null, false, false, true),
            $_title,
            find_overridden_comment_forum('calendar', strval($event['e_type'])),
            $event['e_add_date']
        );

        // Edit URL
        if ((has_actual_page_access(null, 'cms_calendar', null, null)) && (has_edit_permission(($event['e_member_calendar'] !== null) ? 'low' : 'mid', get_member(), $event['e_submitter'], 'cms_calendar', array('calendar', $event['e_type'])))) {
            $edit_url = build_url(array('page' => 'cms_calendar', 'type' => '_edit', 'id' => $id, 'private' => get_param_integer('private', null), 'member_id' => get_param_integer('member_id', null)), get_module_zone('cms_calendar'));
        } else {
            $edit_url = new Tempcode();
        }

        // Work out all our various dates
        $day = get_param_string('day', '');
        if ($day != '') {
            $event = adjust_event_dates_for_a_recurrence($day, $event, ($event['e_do_timezone_conv'] == 1) ? get_users_timezone() : $event['e_timezone']);
        }
        list($time_raw, $from) = find_event_start_timestamp($event);
        $day_formatted = cms_strftime(do_lang('calendar_date_verbose'), $from);
        if ((!is_null($event['e_end_year'])) && (!is_null($event['e_end_month'])) && (!is_null($event['e_end_day']))) {
            list($to_raw, $to) = find_event_end_timestamp($event);

            $to_day_formatted = cms_strftime(do_lang('calendar_date_verbose'), $to);
            $human_readable_time_range = date_range($from, $to, !is_null($event['e_start_hour']));
        } else {
            $to_raw = null;
            $to = null;

            $to_day_formatted = null;
            $human_readable_time_range = is_null($event['e_start_hour']) ? '' : cms_strftime(do_lang('calendar_minute'), $from);
        }

        // Recurrences
        $recurrence = do_lang_tempcode('NA_EM');
        if (($event['e_recurrence'] != 'none') && ($event['e_recurrence'] != '')) {
            $l_code = explode(' ', strtoupper($event['e_recurrence']));
            $recurrence = do_lang_tempcode($l_code[0]);

            if (!is_null($event['e_recurrences'])) {
                $recurrence = do_lang_tempcode('RECURRENCE_ITERATION', $recurrence, make_string_tempcode(escape_html(integer_format($event['e_recurrences']))));
            }
        } elseif ($day == '') {
            $day = $first_date;
        }

        // Views
        if ((get_db_type() != 'xml') && (get_value('no_view_counts') !== '1') && (is_null(get_bot_type()))) {
            $event['e_views']++;
            if (!$GLOBALS['SITE_DB']->table_is_locked('calendar_events')) {
                $GLOBALS['SITE_DB']->query_update('calendar_events', array('e_views' => $event['e_views']), array('id' => $id), '', 1, null, false, true);
            }
        }

        // Output
        $map = array(
            'TITLE' => $this->title,
            '_TITLE' => get_translated_text($event['e_title']),
            'ID' => strval($id),
            'TAGS' => get_loaded_tags('calendar'),
            'WARNING_DETAILS' => $warning_details,
            'SUBMITTER' => strval($event['e_submitter']),
            'ADD_DATE' => get_timezoned_date($event['e_add_date']),
            'ADD_DATE_RAW' => strval($event['e_add_date']),
            'EDIT_DATE_RAW' => is_null($event['e_edit_date']) ? '' : strval($event['e_edit_date']),
            'VIEWS' => integer_format($event['e_views']),
            'LOGO' => $event['t_logo'],
            'DAY' => $day_formatted,
            'TO_DAY' => $to_day_formatted,
            'RECURRENCE' => $recurrence,
            'IS_PUBLIC' => $is_public,
            'MEMBER_CALENDAR' => is_null($event['e_member_calendar']) ? null : strval($event['e_member_calendar']),
            'PRIORITY' => strval($priority),
            'PRIORITY_LANG' => $priority_lang,
            'TYPE' => $type,
            'TIME' => $human_readable_time_range,
            'TIME_RAW' => strval($time_raw),
            'TIME_VCAL' => date('Y-m-d', $time_raw) . ' ' . date('H:i:s', $time_raw),
            'TO_TIME_VCAL' => is_null($to_raw) ? null : (date('Y-m-d', $to_raw) . ' ' . date('H:i:s', $to_raw)),
            'EDIT_URL' => $edit_url,
            'SUBSCRIPTIONS' => $_subscriptions,
            'SUBSCRIBE_URL' => $subscribe_url,
            'BACK_URL' => $back_url,
            'CONTENT' => $content,
            'SUBSCRIBED' => $subscribed,
            'RATING_DETAILS' => $rating_details,
            'TRACKBACK_DETAILS' => $trackback_details,
            'COMMENT_DETAILS' => $comment_details,
            'VALIDATED' => $event['validated'] == 1,
            '_GUID' => '602e6f86f586ef0a24efed950eafd426',
        );
        if ($event['e_do_timezone_conv'] == 0) {
            $timezone_map = get_timezone_list();
            $map['TIMEZONE'] = isset($timezone_map[$event['e_timezone']]) ? $timezone_map[$event['e_timezone']] : null;
        }
        return do_template('CALENDAR_EVENT_SCREEN', $map);
    }

    /**
     * Interface to subscribe for reminders to an event.
     *
     * @return Tempcode The UI
     */
    public function subscribe_event()
    {
        $this->title = get_screen_title('SUBSCRIBE_EVENT');

        // Check access
        $id = get_param_integer('id');
        check_privilege('view_calendar');
        $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];
        if ($event['e_member_calendar'] !== get_member()) {
            if (addon_installed('content_privacy')) {
                require_code('content_privacy');
                check_privacy('event', strval($id));
            }
        }

        if (!has_category_access(get_member(), 'calendar', strval($event['e_type']))) {
            access_denied('CATEGORY_ACCESS');
        }

        require_code('form_templates');

        $post_url = build_url(array('page' => '_SELF', 'type' => '_subscribe_event', 'id' => $id), '_SELF');
        $fields = new Tempcode();
        $fields->attach(form_input_float(do_lang_tempcode('REMINDER_TIME'), do_lang_tempcode('DESCRIPTION_REMINDER_TIME'), 'hours_before', 1.0, true));
        $submit_name = do_lang_tempcode('SUBSCRIBE_EVENT');

        return do_template('FORM_SCREEN', array('_GUID' => '8f6a962617031264ee1af552701804ca', 'SKIP_WEBSTANDARDS' => true, 'HIDDEN' => '', 'TITLE' => $this->title, 'TEXT' => '', 'FIELDS' => $fields, 'URL' => $post_url, 'SUBMIT_ICON' => 'buttons__proceed', 'SUBMIT_NAME' => $submit_name));
    }

    /**
     * Subscribe for reminders to an event.
     *
     * @return Tempcode The UI
     */
    public function _subscribe_event()
    {
        $this->title = get_screen_title('SUBSCRIBE_EVENT');

        $seconds_before = intval(float_unformat(post_param_string('hours_before')) * 3600.0);

        $id = get_param_integer('id'); // The event ID
        $events = $GLOBALS['SITE_DB']->query_select('calendar_events', array('*'), array('id' => get_param_integer('id')), '', 1);
        $event = $events[0];

        $rem_id = $GLOBALS['SITE_DB']->query_insert('calendar_reminders', array(
            'e_id' => $id,
            'n_member_id' => get_member(),
            'n_seconds_before' => $seconds_before
        ), true);

        $privacy_ok = true;
        if (addon_installed('content_privacy')) {
            require_code('content_privacy');
            check_privacy('event', strval($id));
            $privacy_ok = has_privacy_access('event', strval($id), $GLOBALS['FORUM_DRIVER']->get_guest_id());
        }

        require_code('users2');
        if ((has_actual_page_access(get_modal_user(), 'calendar')) && (has_category_access(get_modal_user(), 'calendar', strval($event['e_type']))) && ($privacy_ok)) {
            list(, $from) = find_event_start_timestamp($event);
            $to = mixed();
            if (!is_null($event['e_end_year']) && !is_null($event['e_end_month']) && !is_null($event['e_end_day'])) {
                list(, $to) = find_event_end_timestamp($event);
            }

            require_code('activities');
            require_code('temporal2');
            syndicate_described_activity('calendar:ACTIVITY_SUBSCRIBED_EVENT', get_translated_text($event['e_title']), date_range($from, $to, !is_null($event['e_start_hour'])), make_nice_timezone_name($event['e_timezone']), '_SEARCH:calendar:view:' . strval($id), '', '', 'calendar', 1, null, true);
        }

        // Add next reminder to job system
        $recurrences = 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'], is_null($event['e_start_hour']) ? 0 : $event['e_start_hour'], is_null($event['e_start_minute']) ? 0 : $event['e_start_minute'], $event['e_end_year'], $event['e_end_month'], $event['e_end_day'], $event['e_end_monthly_spec_type'], is_null($event['e_end_hour']) ? 0 : $event['e_end_hour'], is_null($event['e_end_minute']) ? 0 : $event['e_end_minute'], $event['e_recurrence'], min(1, $event['e_recurrences']));
        if (array_key_exists(0, $recurrences)) {
            $GLOBALS['SITE_DB']->query_insert('calendar_jobs', array(
                'j_time' => usertime_to_utctime($recurrences[0][0]) - $seconds_before,
                'j_reminder_id' => $rem_id,
                'j_member_id' => get_member(),
                'j_event_id' => get_param_integer('id')
            ));
        }

        $url = build_url(array('page' => '_SELF', 'type' => 'view', 'id' => get_param_integer('id')), '_SELF');
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Unsubscribe for reminders to an event.
     *
     * @return Tempcode The UI
     */
    public function unsubscribe_event()
    {
        $this->title = get_screen_title('UNSUBSCRIBE_EVENT');

        $GLOBALS['SITE_DB']->query_delete('calendar_reminders', array('id' => get_param_integer('reminder_id')), '', 1); // reminder_id being the reminder ID

        $GLOBALS['SITE_DB']->query_delete('calendar_jobs', array(
            'j_reminder_id' => get_param_integer('reminder_id'),
            'j_member_id' => get_member()
        ));

        $url = build_url(array('page' => '_SELF', 'type' => 'view', 'id' => get_param_integer('id')), '_SELF'); // id being the event ID
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Declare interests for event types.
     *
     * @return Tempcode The UI
     */
    public function interests()
    {
        $types = $GLOBALS['SITE_DB']->query_select('calendar_types', array('id'));

        $GLOBALS['SITE_DB']->query_delete('calendar_interests', array('i_member_id' => get_member()));
        foreach ($types as $type) {
            $status = (post_param_integer('int_' . strval($type['id']), 0) == 1 || (post_param_integer('id', null) === $type['id']));
            if ($status) {
                $GLOBALS['SITE_DB']->query_insert('calendar_interests', array('i_member_id' => get_member(), 't_type' => $type['id']));
            }
        }

        $url = build_url(array('page' => '_SELF', 'type' => 'browse', 'view' => get_param_string('view'), 'id' => get_param_string('id')), '_SELF'); // id being the event ID
        $this->title = get_screen_title('DECLARE_EVENT_INTEREST');
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Declare interest to an event type.
     *
     * @return Tempcode The UI
     */
    public function declare_interest()
    {
        $this->title = get_screen_title('DECLARE_EVENT_INTEREST');

        $GLOBALS['SITE_DB']->query_insert('calendar_interests', array('i_member_id' => get_member(), 't_type' => get_param_integer('t_type')));

        $url = build_url(array('page' => '_SELF', 'type' => 'browse', 'view' => get_param_string('view'), 'id' => get_param_string('id')), '_SELF');
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Undeclare interest to an event type.
     *
     * @return Tempcode The UI
     */
    public function undeclare_interest()
    {
        $this->title = get_screen_title('UNDECLARE_EVENT_INTEREST');

        $GLOBALS['SITE_DB']->query_delete('calendar_interests', array('i_member_id' => get_member(), 't_type' => get_param_string('t_type')), '', 1);

        $url = build_url(array('page' => '_SELF', 'type' => 'browse', 'view' => get_param_string('view'), 'id' => get_param_string('id')), '_SELF');
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }
}
