Example #1
0
    /**
     * Handler for preferences_list hook.
     * Adds options blocks into Calendar settings sections in Preferences.
     *
     * @param array Original parameters
     * @return array Modified parameters
     */
    function preferences_list($p)
    {
        if ($p['section'] != 'calendar') {
            return $p;
        }
        $no_override = array_flip((array) $this->rc->config->get('dont_override'));
        $p['blocks']['view']['name'] = $this->gettext('mainoptions');
        if (!isset($no_override['calendar_default_view'])) {
            if (!$p['current']) {
                $p['blocks']['view']['content'] = true;
                return $p;
            }
            $field_id = 'rcmfd_default_view';
            $select = new html_select(array('name' => '_default_view', 'id' => $field_id));
            $select->add($this->gettext('day'), "agendaDay");
            $select->add($this->gettext('week'), "agendaWeek");
            $select->add($this->gettext('month'), "month");
            $select->add($this->gettext('agenda'), "table");
            $p['blocks']['view']['options']['default_view'] = array('title' => html::label($field_id, Q($this->gettext('default_view'))), 'content' => $select->show($this->rc->config->get('calendar_default_view', $this->defaults['calendar_default_view'])));
        }
        // Begin mod by Rosali (https://issues.kolab.org/show_bug.cgi?id=3481)
        if (!isset($no_override['calendar_treat_as_allday'])) {
            if (!$p['current']) {
                $p['blocks']['view']['content'] = true;
                return $p;
            }
            $field_id = 'rcmfd_calendar_treat_as_allday';
            $select = new html_select(array('name' => '_treat_as_allday', 'id' => $field_id));
            for ($i = 4; $i <= 12; $i++) {
                $select->add($i, $i);
            }
            $p['blocks']['view']['options']['calendar_treat_as_allday'] = array('title' => html::label($field_id, Q($this->gettext('treat_as_allday'))), 'content' => $select->show((int) $this->rc->config->get('calendar_treat_as_allday', 6)) . '&nbsp;' . $this->gettext('hours'));
        }
        // End mod by Rosali
        if (!isset($no_override['calendar_timeslots'])) {
            if (!$p['current']) {
                $p['blocks']['view']['content'] = true;
                return $p;
            }
            $field_id = 'rcmfd_timeslot';
            $choices = array('1', '2', '3', '4', '6');
            $select = new html_select(array('name' => '_timeslots', 'id' => $field_id));
            $select->add($choices);
            $p['blocks']['view']['options']['timeslots'] = array('title' => html::label($field_id, Q($this->gettext('timeslots'))), 'content' => $select->show(strval($this->rc->config->get('calendar_timeslots', $this->defaults['calendar_timeslots']))));
        }
        if (!isset($no_override['calendar_first_day'])) {
            if (!$p['current']) {
                $p['blocks']['view']['content'] = true;
                return $p;
            }
            $field_id = 'rcmfd_firstday';
            $select = new html_select(array('name' => '_first_day', 'id' => $field_id));
            $select->add(rcube_label('sunday'), '0');
            $select->add(rcube_label('monday'), '1');
            $select->add(rcube_label('tuesday'), '2');
            $select->add(rcube_label('wednesday'), '3');
            $select->add(rcube_label('thursday'), '4');
            $select->add(rcube_label('friday'), '5');
            $select->add(rcube_label('saturday'), '6');
            $p['blocks']['view']['options']['first_day'] = array('title' => html::label($field_id, Q($this->gettext('first_day'))), 'content' => $select->show(strval($this->rc->config->get('calendar_first_day', $this->defaults['calendar_first_day']))));
        }
        if (!isset($no_override['calendar_first_hour'])) {
            if (!$p['current']) {
                $p['blocks']['view']['content'] = true;
                return $p;
            }
            $time_format = $this->rc->config->get('time_format', libcalendaring::to_php_date_format($this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format'])));
            $select_hours = new html_select();
            for ($h = 0; $h < 24; $h++) {
                $select_hours->add(date($time_format, mktime($h, 0, 0)), $h);
            }
            $field_id = 'rcmfd_firsthour';
            $p['blocks']['view']['options']['first_hour'] = array('title' => html::label($field_id, Q($this->gettext('first_hour'))), 'content' => $select_hours->show($this->rc->config->get('calendar_first_hour', $this->defaults['calendar_first_hour']), array('name' => '_first_hour', 'id' => $field_id)));
        }
        if (!isset($no_override['calendar_work_start'])) {
            if (!$p['current']) {
                $p['blocks']['view']['content'] = true;
                return $p;
            }
            $field_id = 'rcmfd_workstart';
            $p['blocks']['view']['options']['workinghours'] = array('title' => html::label($field_id, Q($this->gettext('workinghours'))), 'content' => $select_hours->show($this->rc->config->get('calendar_work_start', $this->defaults['calendar_work_start']), array('name' => '_work_start', 'id' => $field_id)) . ' &mdash; ' . $select_hours->show($this->rc->config->get('calendar_work_end', $this->defaults['calendar_work_end']), array('name' => '_work_end', 'id' => $field_id)));
        }
        if (!isset($no_override['calendar_event_coloring'])) {
            if (!$p['current']) {
                $p['blocks']['view']['content'] = true;
                return $p;
            }
            $field_id = 'rcmfd_coloring';
            $select_colors = new html_select(array('name' => '_event_coloring', 'id' => $field_id));
            $select_colors->add($this->gettext('coloringmode0'), 0);
            $select_colors->add($this->gettext('coloringmode1'), 1);
            $select_colors->add($this->gettext('coloringmode2'), 2);
            $select_colors->add($this->gettext('coloringmode3'), 3);
            $p['blocks']['view']['options']['eventcolors'] = array('title' => html::label($field_id . 'value', Q($this->gettext('eventcoloring'))), 'content' => $select_colors->show($this->rc->config->get('calendar_event_coloring', $this->defaults['calendar_event_coloring'])));
        }
        if (!isset($no_override['calendar_default_alarm_type'])) {
            if (!$p['current']) {
                $p['blocks']['view']['content'] = true;
                return $p;
            }
            $field_id = 'rcmfd_alarm';
            $select_type = new html_select(array('name' => '_alarm_type', 'id' => $field_id));
            $select_type->add($this->gettext('none'), '');
            $types = array();
            foreach ($this->get_drivers() as $driver) {
                foreach ($driver->alarm_types as $type) {
                    $types[$type] = $type;
                }
            }
            foreach ($types as $type) {
                $select_type->add(rcube_label(strtolower("alarm{$type}option"), 'libcalendaring'), $type);
            }
            $p['blocks']['view']['options']['alarmtype'] = array('title' => html::label($field_id, Q($this->gettext('defaultalarmtype'))), 'content' => $select_type->show($this->rc->config->get('calendar_default_alarm_type', '')));
        }
        if (!isset($no_override['calendar_default_alarm_offset'])) {
            if (!$p['current']) {
                $p['blocks']['view']['content'] = true;
                return $p;
            }
            $field_id = 'rcmfd_alarm';
            $input_value = new html_inputfield(array('name' => '_alarm_value', 'id' => $field_id . 'value', 'size' => 3));
            $select_offset = new html_select(array('name' => '_alarm_offset', 'id' => $field_id . 'offset'));
            foreach (array('-M', '-H', '-D', '+M', '+H', '+D') as $trigger) {
                $select_offset->add(rcube_label('trigger' . $trigger, 'libcalendaring'), $trigger);
            }
            $preset = libcalendaring::parse_alaram_value($this->rc->config->get('calendar_default_alarm_offset', '-15M'));
            $p['blocks']['view']['options']['alarmoffset'] = array('title' => html::label($field_id . 'value', Q($this->gettext('defaultalarmoffset'))), 'content' => $input_value->show($preset[0]) . ' ' . $select_offset->show($preset[1]));
        }
        if (!isset($no_override['calendar_default_calendar'])) {
            if (!$p['current']) {
                $p['blocks']['view']['content'] = true;
                return $p;
            }
            // default calendar selection
            $field_id = 'rcmfd_default_calendar';
            $select_cal = new html_select(array('name' => '_default_calendar', 'id' => $field_id, 'is_escaped' => true));
            // Begin mod by Rosali (avoid duplicates if using common database tables - detect readonly - https://gitlab.awesome-it.de/kolab/roundcube-plugins/issues/34)
            $options = array();
            foreach ($this->get_drivers() as $driver) {
                foreach ((array) $driver->list_calendars(false, true) as $id => $prop) {
                    $options[$id] = array_merge((array) $prop, (array) $options[$id]);
                    if ($prop['default']) {
                        $default_calendar = $id;
                    }
                }
            }
            foreach ($options as $id => $prop) {
                if ($prop['readonly'] != true) {
                    $select_cal->add($prop['name'], strval($id));
                }
            }
            // End mod by Rosali
            $p['blocks']['view']['options']['defaultcalendar'] = array('title' => html::label($field_id . 'value', Q($this->gettext('defaultcalendar'))), 'content' => $select_cal->show($this->rc->config->get('calendar_default_calendar', $default_calendar)));
        }
        // category definitions
        // TODO: Currently uses 'kolab' driver: This should be done for each driver, see preferences_save().
        foreach ($this->get_drivers() as $driver) {
            if (!$driver->nocategories && !isset($no_override['calendar_categories'])) {
                $p['blocks']['categories']['name'] = $this->gettext('categories');
                if (!$p['current']) {
                    $p['blocks']['categories']['content'] = true;
                    return $p;
                }
                $categories = (array) $driver->list_categories();
                $categories_list = '';
                foreach ($categories as $name => $color) {
                    $key = md5($name);
                    $field_class = 'rcmfd_category_' . str_replace(' ', '_', $name);
                    $category_remove = new html_inputfield(array('type' => 'button', 'value' => 'X', 'class' => 'button', 'onclick' => '$(this).parent().remove()', 'title' => $this->gettext('remove_category')));
                    $category_name = new html_inputfield(array('name' => "_categories[{$key}]", 'class' => $field_class, 'size' => 30, 'disabled' => $driver->categoriesimmutable));
                    $category_color = new html_inputfield(array('name' => "_colors[{$key}]", 'class' => "{$field_class} colors", 'size' => 6));
                    $hidden = $driver->categoriesimmutable ? html::tag('input', array('type' => 'hidden', 'name' => "_categories[{$key}]", 'value' => $name)) : '';
                    $categories_list .= html::div(null, $hidden . $category_name->show($name) . '&nbsp;' . $category_color->show($color) . '&nbsp;' . $category_remove->show());
                }
                $p['blocks']['categories']['options']['category_' . $name] = array('content' => html::div(array('id' => 'calendarcategories'), $categories_list));
                $field_id = 'rcmfd_new_category';
                $new_category = new html_inputfield(array('name' => '_new_category', 'id' => $field_id, 'size' => 30));
                $add_category = new html_inputfield(array('type' => 'button', 'class' => 'button', 'value' => $this->gettext('add_category'), 'onclick' => "rcube_calendar_add_category()"));
                $p['blocks']['categories']['options']['categories'] = array('content' => $new_category->show('') . '&nbsp;' . $add_category->show());
                $this->rc->output->add_script('function rcube_calendar_add_category(){
		      var name = $("#rcmfd_new_category").val();
		      if (name.length) {
		        var input = $("<input>").attr("type", "text").attr("name", "_categories[]").attr("size", 30).val(name);
		        var color = $("<input>").attr("type", "text").attr("name", "_colors[]").attr("size", 6).addClass("colors").val("000000");
		        var button = $("<input>").attr("type", "button").attr("value", "X").addClass("button").click(function(){ $(this).parent().remove() });
		        $("<div>").append(input).append("&nbsp;").append(color).append("&nbsp;").append(button).appendTo("#calendarcategories");
		        color.miniColors({ colorValues:(rcmail.env.mscolors || []) });
		        $("#rcmfd_new_category").val("");
		      }
		    }');
                $this->rc->output->add_script('$("#rcmfd_new_category").keypress(function(event){
		      if (event.which == 13) {
		        rcube_calendar_add_category();
		        event.preventDefault();
		      }
		    });
		    ', 'docready');
                // include color picker
                $this->rc->output->add_header(html::tag('script', array('type' => 'text/javascript', 'src' => 'plugins/libgpl/calendar/lib/js/jquery.miniColors.min.js')));
                $this->include_stylesheet($this->local_skin_path() . '/jquery.miniColors.css');
                $this->rc->output->set_env('mscolors', $driver->get_color_values());
                $this->rc->output->add_script('$("input.colors").miniColors({ colorValues:rcmail.env.mscolors })', 'docready');
            }
        }
        return $p;
    }
Example #2
0
 /**
  * Build a valid iCal format block from the given event
  *
  * @param  array    Hash array with event/task properties from libkolab
  * @param  object   VCalendar object to append event to or false for directly sending data to stdout
  * @param  callable Callback function to fetch attachment contents, false if no attachment export
  * @param  object   RECURRENCE-ID property when serializing a recurrence exception
  */
 private function _to_ical($event, $vcal, $get_attachment, $recurrence_id = null)
 {
     $type = $event['_type'] ?: 'event';
     $ve = VObject\Component::create($this->type_component_map[$type]);
     $ve->add('UID', $event['uid']);
     // set DTSTAMP according to RFC 5545, 3.8.7.2.
     $dtstamp = !empty($event['changed']) && !empty($this->method) ? $event['changed'] : new DateTime();
     $ve->add(self::datetime_prop('DTSTAMP', $dtstamp, true));
     // all-day events end the next day
     if ($event['allday'] && !empty($event['end'])) {
         $event['end'] = clone $event['end'];
         $event['end']->add(new \DateInterval('P1D'));
         $event['end']->_dateonly = true;
     }
     if (!empty($event['created'])) {
         $ve->add(self::datetime_prop('CREATED', $event['created'], true));
     }
     if (!empty($event['changed'])) {
         $ve->add(self::datetime_prop('LAST-MODIFIED', $event['changed'], true));
     }
     if (!empty($event['start'])) {
         $ve->add(self::datetime_prop('DTSTART', $event['start'], false, (bool) $event['allday']));
     }
     if (!empty($event['end'])) {
         $ve->add(self::datetime_prop('DTEND', $event['end'], false, (bool) $event['allday']));
     }
     if (!empty($event['due'])) {
         $ve->add(self::datetime_prop('DUE', $event['due'], false));
     }
     if ($recurrence_id) {
         $ve->add($recurrence_id);
     }
     $ve->add('SUMMARY', $event['title']);
     if ($event['location']) {
         $ve->add($this->is_apple() ? new vobject_location_property('LOCATION', $event['location']) : new VObject\Property('LOCATION', $event['location']));
     }
     if ($event['description']) {
         $ve->add('DESCRIPTION', strtr($event['description'], array("\r\n" => "\n", "\r" => "\n")));
     }
     // normalize line endings
     if ($event['sequence']) {
         $ve->add('SEQUENCE', $event['sequence']);
     }
     if ($event['recurrence'] && !$recurrence_id) {
         if ($exdates = $event['recurrence']['EXDATE']) {
             unset($event['recurrence']['EXDATE']);
             // don't serialize EXDATEs into RRULE value
         }
         if ($rdates = $event['recurrence']['RDATE']) {
             unset($event['recurrence']['RDATE']);
             // don't serialize RDATEs into RRULE value
         }
         if ($event['recurrence']['FREQ']) {
             $ve->add('RRULE', libcalendaring::to_rrule($event['recurrence']));
         }
         // add EXDATEs each one per line (for Thunderbird Lightning)
         // Begin mod by Rosali (handle EXDATE the same as RDATE)
         if (!empty($exdates)) {
             $exdates = array_values($exdates);
             $sample = self::datetime_prop('EXDATE', $exdates[0]);
             $edprop = new VObject\Property\MultiDateTime('EXDATE', null);
             $edprop->setDateTimes($exdates, $sample->getDateType());
             $ve->add($edprop);
         }
         // End mod by Rosali
         // add RDATEs
         if (!empty($rdates)) {
             $sample = self::datetime_prop('RDATE', $rdates[0]);
             $rdprop = new VObject\Property\MultiDateTime('RDATE', null);
             $rdprop->setDateTimes($rdates, $sample->getDateType());
             $ve->add($rdprop);
         }
     }
     if ($event['categories']) {
         $cat = VObject\Property::create('CATEGORIES');
         $cat->setParts((array) $event['categories']);
         $ve->add($cat);
     }
     if (!empty($event['free_busy'])) {
         $ve->add('TRANSP', $event['free_busy'] == 'free' ? 'TRANSPARENT' : 'OPAQUE');
         // for Outlook clients we provide the X-MICROSOFT-CDO-BUSYSTATUS property
         if (stripos($this->agent, 'outlook') !== false) {
             $ve->add('X-MICROSOFT-CDO-BUSYSTATUS', $event['free_busy'] == 'outofoffice' ? 'OOF' : strtoupper($event['free_busy']));
         }
     }
     // Begin mod by Rosali (adjust float value to percent)
     if ($event['complete'] && $event['complete'] <= 1) {
         $event['complete'] = round($event['complete'] * 100, 0);
     }
     // End mod by Rosali
     if ($event['priority']) {
         $ve->add('PRIORITY', $event['priority']);
     }
     if ($event['cancelled']) {
         $ve->add('STATUS', 'CANCELLED');
     } else {
         if ($event['free_busy'] == 'tentative') {
             $ve->add('STATUS', 'TENTATIVE');
         } else {
             if ($event['complete'] == 100) {
                 $ve->add('STATUS', 'COMPLETED');
             } else {
                 if ($event['status']) {
                     $ve->add('STATUS', $event['status']);
                 }
             }
         }
     }
     // End mod by Rosali
     if (!empty($event['sensitivity'])) {
         $ve->add('CLASS', strtoupper($event['sensitivity']));
     }
     if (!empty($event['complete'])) {
         $ve->add('PERCENT-COMPLETE', intval($event['complete']));
         // Apple iCal required the COMPLETED date to be set in order to consider a task complete
         if ($event['complete'] == 100) {
             $ve->add(self::datetime_prop('COMPLETED', $event['changed'] ?: new DateTime('now - 1 hour'), true));
         }
     }
     if ($event['alarms']) {
         $va = VObject\Component::create('VALARM');
         list($trigger, $va->action) = explode(':', $event['alarms']);
         $val = libcalendaring::parse_alaram_value($trigger);
         $period = $val[1] && preg_match('/[HMS]$/', $val[1]) ? 'PT' : 'P';
         if ($val[1]) {
             $va->add('TRIGGER', preg_replace('/^([-+])P?T?(.+)/', "\\1{$period}\\2", $trigger));
         } else {
             $va->add('TRIGGER', gmdate('Ymd\\THis\\Z', $val[0]), array('VALUE' => 'DATE-TIME'));
         }
         $ve->add($va);
     }
     foreach ((array) $event['attendees'] as $attendee) {
         if (is_array($attendee)) {
             // Mod by Rosali (check type array)
             if ($attendee['role'] == 'ORGANIZER') {
                 if (empty($event['organizer'])) {
                     $event['organizer'] = $attendee;
                 }
             } else {
                 if (!empty($attendee['email'])) {
                     $attendee['rsvp'] = $attendee['rsvp'] ? 'TRUE' : null;
                     $ve->add('ATTENDEE', 'mailto:' . $attendee['email'], array_filter(self::map_keys($attendee, $this->attendee_keymap)));
                 }
             }
         }
     }
     if ($event['organizer']) {
         $ve->add('ORGANIZER', 'mailto:' . $event['organizer']['email'], self::map_keys($event['organizer'], array('name' => 'CN')));
     }
     foreach ((array) $event['url'] as $url) {
         if (!empty($url)) {
             $ve->add('URL', $url);
         }
     }
     if (!empty($event['parent_id'])) {
         $ve->add('RELATED-TO', $event['parent_id'], array('RELTYPE' => 'PARENT'));
     }
     // export attachments
     if (!empty($event['attachments'])) {
         foreach ((array) $event['attachments'] as $attach) {
             // check available memory and skip attachment export if we can't buffer it
             if (is_callable($get_attachment) && $memory_limit > 0 && ($memory_used = function_exists('memory_get_usage') ? memory_get_usage() : 16 * 1024 * 1024) && $attach['size'] && $memory_used + $attach['size'] * 3 > $memory_limit) {
                 continue;
             }
             // embed attachments using the given callback function
             if (is_callable($get_attachment) && ($data = call_user_func($get_attachment, $attach['id'], $event))) {
                 // embed attachments for iCal
                 $ve->add('ATTACH', base64_encode($data), array_filter(array('VALUE' => 'BINARY', 'ENCODING' => 'BASE64', 'FMTTYPE' => $attach['mimetype'], 'X-LABEL' => $attach['name'])));
                 unset($data);
                 // attempt to free memory
             } else {
                 if (!empty($this->attach_uri)) {
                     $ve->add('ATTACH', strtr($this->attach_uri, array('{{id}}' => urlencode($attach['id']), '{{name}}' => urlencode($attach['name']), '{{mimetype}}' => urlencode($attach['mimetype']))), array('FMTTYPE' => $attach['mimetype'], 'VALUE' => 'URI'));
                 }
             }
         }
     }
     foreach ((array) $event['links'] as $uri) {
         $ve->add('ATTACH', $uri);
     }
     // add custom properties
     foreach ((array) $event['x-custom'] as $prop) {
         $ve->add($prop[0], $prop[1]);
     }
     // append to vcalendar container
     if ($vcal) {
         $vcal->add($ve);
     } else {
         // serialize and send to stdout
         echo $ve->serialize();
     }
     // append recurrence exceptions
     if (isset($event['recurrence']['EXCEPTIONS']) && is_array($event['recurrence']['EXCEPTIONS'])) {
         foreach ($event['recurrence']['EXCEPTIONS'] as $ex) {
             $exdate = clone $event['start'];
             $exdate->setDate($ex['start']->format('Y'), $ex['start']->format('n'), $ex['start']->format('j'));
             $recurrence_id = self::datetime_prop('RECURRENCE-ID', $exdate, true);
             // if ($ex['thisandfuture'])  // not supported by any client :-(
             //    $recurrence_id->add('RANGE', 'THISANDFUTURE');
             $this->_to_ical($ex, $vcal, $get_attachment, $recurrence_id);
         }
     }
 }