Projet

Général

Profil

Paste
Télécharger (18,8 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / date_ical / includes / date_ical_plugin_style_ical_feed.inc @ 62e0cc08

1 85ad3d82 Assos Assos
<?php
2
/**
3
 * @file
4
 * Views style plugin for the Date iCal module.
5
 */
6
7
/**
8 8a7e43dd Florent Torregrosa
 * Defines a Views style plugin that renders iCal feeds.
9 85ad3d82 Assos Assos
 */
10
class date_ical_plugin_style_ical_feed extends views_plugin_style {
11 204e4d33 Assos Assos
12 8a7e43dd Florent Torregrosa
  /**
13
   * Internal helper function.
14
   */
15 85ad3d82 Assos Assos
  protected function _get_option($option_name) {
16
    return isset($this->options[$option_name]) ? $this->options[$option_name] : '';
17
  }
18 204e4d33 Assos Assos
19 8a7e43dd Florent Torregrosa
  /**
20
   * Sets up the iCal feed icon on calendar pages.
21
   */
22
  public function attach_to($display_id, $path, $title) {
23 85ad3d82 Assos Assos
    $url_options = array();
24
    $input = $this->view->get_exposed_input();
25
    if ($input) {
26
      $url_options['query'] = $input;
27
    }
28
    $url_options['absolute'] = TRUE;
29 204e4d33 Assos Assos
30 62e0cc08 Assos Assos
    // Only add arguments to ical path for the contextual filters in the feed display.
31
    // Clone view to prevent affecting the page view where this is attached.
32
    $clone = $this->view->clone_view();
33
    $clone->set_display($this->display->id);
34
    $contextual_filters = $clone->get_items('argument');
35
    $arg_number = count($contextual_filters);
36
37
    // Only include as many arguments as the feed display supports.
38
    foreach($clone->args as $key => $arg) {
39
      if ($key >= $arg_number) {
40
        unset($clone->args[$key]);
41
      }
42
    }
43
44
    $url = url($clone->get_url(NULL, $path), $url_options);
45
46 8a7e43dd Florent Torregrosa
    // If the user didn't disable the option, change the scheme to webcal://
47
    // so calendar clients can automatically subscribe via the iCal link.
48 85ad3d82 Assos Assos
    if (!$this->_get_option('disable_webcal')) {
49
      $url = str_replace(array('http://', 'https://'), 'webcal://', $url);
50
    }
51 204e4d33 Assos Assos
52 62e0cc08 Assos Assos
    // Render the feed icon and header tag (except during a View Preview and
53
    // if the display is disabled).
54
    if (empty($this->view->live_preview) && (!isset($this->display->display_options['enabled']) || $this->display->display_options['enabled'])) {
55 85ad3d82 Assos Assos
      $tooltip = t('Add to My Calendar');
56
      if (!isset($this->view->feed_icon)) {
57 8a7e43dd Florent Torregrosa
        // In PHP 5.5, you're not allowed to concatinate onto an unset
58
        // property. But we need to do a concat, because there may be
59
        // other attachments.
60 85ad3d82 Assos Assos
        $this->view->feed_icon = '';
61
      }
62 8a7e43dd Florent Torregrosa
      $variables = array(
63
        'url' => check_url($url),
64
        'tooltip' => $tooltip,
65
        'view' => $this->view,
66
      );
67
      $this->view->feed_icon .= theme('date_ical_icon', $variables);
68 85ad3d82 Assos Assos
      drupal_add_html_head_link(array(
69
        'rel' => 'alternate',
70
        'type' => 'text/calendar',
71
        'title' => $tooltip,
72 8a7e43dd Florent Torregrosa
        'href' => $url,
73 85ad3d82 Assos Assos
      ));
74
    }
75
  }
76 204e4d33 Assos Assos
77 8a7e43dd Florent Torregrosa
  /**
78
   * Set up the options for the style plugin.
79
   */
80
  public function option_definition() {
81
    $false_bool = array('default' => FALSE, 'bool' => TRUE);
82 204e4d33 Assos Assos
83 85ad3d82 Assos Assos
    $options = parent::option_definition();
84
    $options['cal_name'] = array('default' => array());
85 8a7e43dd Florent Torregrosa
    $options['no_calname'] = $false_bool;
86
    $options['disable_webcal'] = $false_bool;
87
    $options['exclude_dtstamp'] = $false_bool;
88
    $options['unescape_punctuation'] = $false_bool;
89 85ad3d82 Assos Assos
    return $options;
90
  }
91 204e4d33 Assos Assos
92 8a7e43dd Florent Torregrosa
  /**
93
   * Build the form for setting the style plugin's options.
94
   */
95
  public function options_form(&$form, &$form_state) {
96 85ad3d82 Assos Assos
    parent::options_form($form, $form_state);
97
    // Allow users to override the default Calendar name (X-WR-CALNAME).
98
    $form['cal_name'] = array(
99
      '#type' => 'textfield',
100
      '#title' => t('iCal Calendar Name'),
101
      '#default_value' => $this->_get_option('cal_name'),
102
      '#description' => t('This will appear as the title of the iCal feed. If left blank, the View Title will be used.
103
        If that is also blank, the site name will be inserted as the iCal feed title.'),
104
    );
105
    $form['no_calname'] = array(
106
      '#type' => 'checkbox',
107
      '#title' => t('Exclude Calendar Name'),
108
      '#default_value' => $this->_get_option('no_calname'),
109
      '#description' => t("Excluding the X-WR-CALNAME value from the iCal Feed causes
110
        some calendar clients to add the events in the feed to an existing calendar, rather
111
        than creating a whole new calendar for them."),
112
    );
113
    $form['disable_webcal'] = array(
114
      '#type' => 'checkbox',
115
      '#title' => t('Disable webcal://'),
116
      '#default_value' => $this->_get_option('disable_webcal'),
117
      '#description' => t("By default, the feed URL will use the webcal:// scheme, which allows calendar
118
        clients to easily subscribe to the feed. If you want your users to instead download this iCal
119
        feed as a file, activate this option."),
120
    );
121
    $form['exclude_dtstamp'] = array(
122
      '#type' => 'checkbox',
123
      '#title' => t('Exclude DTSTAMP'),
124
      '#default_value' => $this->_get_option('exclude_dtstamp'),
125
      '#description' => t("By default, the feed will set each event's DTSTAMP property to the time at which the feed got downloaded.
126
        Some feed readers will (incorrectly) look at the DTSTAMP value when they compare different downloads of the same feed, and
127
        conclcude that the event has been updated (even though it hasn't actually changed). Enable this option to exclude the DTSTAMP
128
        field from your feeds, so that these buggy feed readers won't mark every event as updated every time they check."),
129
    );
130 8a7e43dd Florent Torregrosa
    $form['unescape_punctuation'] = array(
131
      '#type' => 'checkbox',
132
      '#title' => t('Unescape Commas and Semicolons'),
133
      '#default_value' => $this->_get_option('unescape_punctuation'),
134
      '#description' => t('In order to comply with the iCal spec, Date iCal will "escape" commas and semicolons (prepend them with backslashes).
135
        However, many calendar clients are bugged to not unescape these characters, leaving the backslashes littered throughout your events.
136
        Enable this option to have Date iCal unescape these characters before it exports the iCal feed.'),
137
    );
138 85ad3d82 Assos Assos
  }
139 204e4d33 Assos Assos
140 8a7e43dd Florent Torregrosa
  /**
141
   * Render the event arrays returned by the row plugin into a VCALENDAR.
142
   */
143
  public function render() {
144 85ad3d82 Assos Assos
    if (empty($this->row_plugin) || !in_array($this->row_plugin->plugin_name, array('date_ical', 'date_ical_fields'))) {
145
      debug('date_ical_plugin_style_ical_feed: This style plugin supports only the "iCal Entity" and "iCal Fields" row plugins.', NULL, TRUE);
146 8a7e43dd Florent Torregrosa
      return t('To enable iCal output, the view Format must be configured to Show: iCal Entity or iCal Fields.');
147 85ad3d82 Assos Assos
    }
148
    if ($this->row_plugin->plugin_name == 'date_ical_fields' && empty($this->row_plugin->options['date_field'])) {
149 8a7e43dd Florent Torregrosa
      // Because the Date field is required by the form, this error state will
150
      // rarely occur. But I ran across it during testing, and the error that
151
      // resulted was totally non-sensical, so I'm adding this just in case.
152 85ad3d82 Assos Assos
      return t("When using the iCal Fields row plugin, the Date field is required. Please set it up using the Settings link under 'Format -> Show: iCal Fields'.");
153
    }
154
    $events = array();
155
    foreach ($this->view->result as $row_index => $row) {
156
      $this->view->row_index = $row_index;
157 8a7e43dd Florent Torregrosa
      $row->index = $row_index;
158 85ad3d82 Assos Assos
      try {
159 8a7e43dd Florent Torregrosa
        $events[] = $this->row_plugin->render($row);
160 85ad3d82 Assos Assos
      }
161
      catch (Exception $e) {
162
        debug($e->getMessage(), NULL, TRUE);
163
        return $e->getMessage();
164
      }
165
    }
166
    unset($this->view->row_index);
167 204e4d33 Assos Assos
168 85ad3d82 Assos Assos
    // Try to load the iCalcreator library.
169
    $library = libraries_load('iCalcreator');
170
    if (!$library['loaded']) {
171
      // The iCalcreator library isn't available, so we can't output anything.
172
      $output = t('Please install the iCalcreator library to enable iCal output.');
173
    }
174
    else {
175
      // Create a vcalendar object using the iCalcreator library.
176 d756b39a Assos Assos
      $config = array('unique_id' => 'Date iCal');
177 85ad3d82 Assos Assos
      $vcalendar = new vcalendar($config);
178
      $vcalendar->setMethod('PUBLISH');
179 204e4d33 Assos Assos
180 8a7e43dd Florent Torregrosa
      // Only include the X-WR-CALNAME property if the user didn't enable
181
      // the "Exclude Calendar Name" option.
182
      if (!$this->_get_option('no_calname')) {
183
        $cal_name = $this->_get_option('cal_name');
184
        if (empty($cal_name)) {
185
          $cal_name = $this->view->get_title();
186
          if (empty($cal_name)) {
187
            $cal_name = variable_get('site_name', 'Drupal');
188
          }
189 85ad3d82 Assos Assos
        }
190 8a7e43dd Florent Torregrosa
        if (!empty($cal_name)) {
191
          $vcalendar->setProperty('X-WR-CALNAME', $cal_name, array('VALUE' => 'TEXT'));
192 85ad3d82 Assos Assos
        }
193
      }
194 204e4d33 Assos Assos
195 85ad3d82 Assos Assos
      // Now add the VEVENTs.
196
      $timezones = array();
197
      foreach ($events as $event) {
198
        if (empty($event)) {
199
          // The row plugin returned NULL for this row, which can happen due to
200
          // either various error conditions, or because an RRULE is involved.
201
          // When this happens, just skip it.
202
          continue;
203
        }
204 204e4d33 Assos Assos
205 85ad3d82 Assos Assos
        $vevent = $vcalendar->newComponent('vevent');
206 8a7e43dd Florent Torregrosa
        $vevent->setUid($event['uid']);
207
        $vevent->setSummary($event['summary']);
208 204e4d33 Assos Assos
209 85ad3d82 Assos Assos
        // Get the start date as an array.
210
        $start = $event['start']->toArray();
211 8a7e43dd Florent Torregrosa
        $start_timezone = $event['start']->getTimezone()->getName();
212
        $timezones[$start_timezone] = $start_timezone;
213 204e4d33 Assos Assos
214 85ad3d82 Assos Assos
        if ($event['all_day']) {
215 8a7e43dd Florent Torregrosa
          // All Day events need to be DATEs, rather than DATE-TIMEs.
216 85ad3d82 Assos Assos
          $vevent->setDtstart($start['year'], $start['month'], $start['day'],
217
            FALSE, FALSE, FALSE, FALSE, array('VALUE' => 'DATE'));
218
        }
219
        else {
220
          $vevent->setDtstart(
221
            $start['year'],
222
            $start['month'],
223
            $start['day'],
224
            $start['hour'],
225
            $start['minute'],
226
            $start['second'],
227 8a7e43dd Florent Torregrosa
            $start_timezone
228 85ad3d82 Assos Assos
          );
229
        }
230 204e4d33 Assos Assos
231 85ad3d82 Assos Assos
        // Add the Timezone info to the start date, for use later.
232
        $start['tz'] = $event['start']->getTimezone();
233 204e4d33 Assos Assos
234 85ad3d82 Assos Assos
        // Only add the end date if there is one.
235
        if (!empty($event['end'])) {
236
          $end = $event['end']->toArray();
237 8a7e43dd Florent Torregrosa
          $end_timezone = $event['end']->getTimezone()->getName();
238
          $timezones[$end_timezone] = $end_timezone;
239 204e4d33 Assos Assos
240 85ad3d82 Assos Assos
          if ($event['all_day']) {
241
            $vevent->setDtend($end['year'], $end['month'], $end['day'],
242
              FALSE, FALSE, FALSE, FALSE, array('VALUE' => 'DATE'));
243
          }
244
          else {
245
            $vevent->setDtend(
246
              $end['year'],
247
              $end['month'],
248
              $end['day'],
249
              $end['hour'],
250
              $end['minute'],
251
              $end['second'],
252 8a7e43dd Florent Torregrosa
              $end_timezone
253 85ad3d82 Assos Assos
            );
254
          }
255
          $end['tz'] = $event['end']->getTimezone();
256
        }
257 204e4d33 Assos Assos
258 85ad3d82 Assos Assos
        // Handle repeating dates from the date_repeat module.
259
        if (!empty($event['rrule']) && module_exists('date_repeat')) {
260
          // Split the rrule into an RRULE and any additions and exceptions.
261
          module_load_include('inc', 'date_api', 'date_api_ical');
262
          module_load_include('inc', 'date_repeat', 'date_repeat_calc');
263
          list($rrule, $exceptions, $additions) = date_repeat_split_rrule($event['rrule']);
264 204e4d33 Assos Assos
265 85ad3d82 Assos Assos
          // Add the RRULE itself. We need to massage the data a bit, since
266
          // iCalcreator expects RRULEs to be in a different format than how
267
          // Date API gives them to us.
268 8a7e43dd Florent Torregrosa
          $vevent->setRrule(_date_ical_convert_rrule_for_icalcreator($rrule));
269 204e4d33 Assos Assos
270 85ad3d82 Assos Assos
          // Convert any exceptions to EXDATE properties.
271
          if (!empty($exceptions)) {
272
            $exdates = array();
273
            foreach ($exceptions as $exception) {
274
              $except = date_ical_date($exception, 'UTC');
275 204e4d33 Assos Assos
              $except->setTimezone($start['tz']);
276 85ad3d82 Assos Assos
              $exception_array = $except->toArray();
277
              $exdates[] = array(
278
                'year' =>  $exception_array['year'],
279
                'month' => $exception_array['month'],
280
                'day' =>   $exception_array['day'],
281
                // Use the time information from the start date, since Date
282
                // doesn't store time info for EXDATEs.
283
                'hour' =>   $start['hour'],
284
                'min' =>    $start['minute'],
285
                'second' => $start['second'],
286
                'tz' =>     $start['tz']->getName(),
287
              );
288
            }
289
            // Add each exclusion as a separate EXDATE property.
290
            // The spec supports putting multiple date values into one EXDATE,
291
            // but several popular calendar clients (*cough* Apple *cough*)
292
            // are bugged, and do not recognize multi-value EXDATEs.
293 62e0cc08 Assos Assos
            $value = $event['all_day'] == 1 ? "DATE" : "DATE-TIME";
294 85ad3d82 Assos Assos
            foreach ($exdates as $exdate) {
295 62e0cc08 Assos Assos
              $vevent->setExdate(array($exdate), array( "VALUE" => $value ));
296 85ad3d82 Assos Assos
            }
297
          }
298 204e4d33 Assos Assos
299 85ad3d82 Assos Assos
          // Convert any additions to RDATE properties.
300
          if (!empty($additions)) {
301
            $rdates = array();
302
            foreach ($additions as $addition) {
303
              $add = date_ical_date($addition, 'UTC');
304 204e4d33 Assos Assos
              $add->setTimezone($start['tz']);
305 85ad3d82 Assos Assos
              $addition_array = $add->toArray();
306 204e4d33 Assos Assos
307 85ad3d82 Assos Assos
              $rdate = array(
308
                'year' =>  $addition_array['year'],
309
                'month' => $addition_array['month'],
310
                'day' =>   $addition_array['day'],
311 c7b88c87 Assos Assos
                // If the user's copy of Date has support for time in RDATEs,
312
                // use that. Otherwise use the time from the start date.
313 204e4d33 Assos Assos
                'hour' =>   !empty($addition_array['hour']) ? $addition_array['hour'] : $start['hour'],
314
                'min' =>    !empty($addition_array['minute']) ? $addition_array['minute'] : $start['minute'],
315
                'second' => !empty($addition_array['second']) ? $addition_array['second'] : $start['second'],
316 85ad3d82 Assos Assos
                'tz' =>     $start['tz']->getName(),
317
              );
318 204e4d33 Assos Assos
319 85ad3d82 Assos Assos
              // If an end date was was calculated above, use that too.
320
              // iCalcreator expects RDATEs that have end dates to be
321
              // specified as array($start_rdate, $end_rdate).
322
              if (isset($end)) {
323
                $rdate_with_end = array($rdate);
324
                $rdate_with_end[] = array(
325
                  'year' =>  $addition_array['year'],
326
                  'month' => $addition_array['month'],
327
                  'day' =>   $addition_array['day'],
328 c7b88c87 Assos Assos
                  // If the user's copy of Date has support for time in RDATEs,
329
                  // use that. Otherwise use the time from the end date.
330 204e4d33 Assos Assos
                  'hour' =>   !empty($addition_array['hour']) ? $addition_array['hour'] : $end['hour'],
331
                  'min' =>    !empty($addition_array['minute']) ? $addition_array['minute'] : $end['minute'],
332
                  'second' => !empty($addition_array['second']) ? $addition_array['second'] : $end['second'],
333 85ad3d82 Assos Assos
                  'tz' =>     $end['tz']->getName(),
334
                );
335
                $rdate = $rdate_with_end;
336
              }
337 204e4d33 Assos Assos
338 85ad3d82 Assos Assos
              $rdates[] = $rdate;
339
            }
340
            // Add each addition as a separate RDATE property.
341
            // The spec supports putting multiple date values into one RDATE,
342
            // but several popular calendar clients (*cough* Apple *cough*)
343
            // are bugged, and do not recognize multi-value RDATEs.
344
            foreach ($rdates as $rdate) {
345
              $vevent->setRdate(array($rdate));
346
            }
347
          }
348
        }
349
        if (!empty($event['url'])) {
350
          $vevent->setUrl($event['url'], array('type' => 'URI'));
351
        }
352
        if (!empty($event['location'])) {
353 8a7e43dd Florent Torregrosa
          $vevent->setLocation($event['location']);
354 85ad3d82 Assos Assos
        }
355
        if (!empty($event['description'])) {
356 8a7e43dd Florent Torregrosa
          $vevent->setDescription($event['description']);
357 85ad3d82 Assos Assos
        }
358 62e0cc08 Assos Assos
        if (!empty($event['categories'])) {
359
          $vevent->setCategories($event['categories']);
360
        }
361 85ad3d82 Assos Assos
        if (!empty($event['last-modified'])) {
362
          $lm = $event['last-modified']->toArray();
363
          $vevent->setLastModified(
364
            $lm['year'],
365
            $lm['month'],
366
            $lm['day'],
367
            $lm['hour'],
368
            $lm['minute'],
369
            $lm['second'],
370
            $lm['timezone']
371
          );
372
        }
373
        if (!empty($event['created'])) {
374
          $created = $event['created']->toArray();
375
          $vevent->setCreated(
376
            $created['year'],
377
            $created['month'],
378
            $created['day'],
379
            $created['hour'],
380
            $created['minute'],
381
            $created['second'],
382
            $created['timezone']
383
          );
384
        }
385 204e4d33 Assos Assos
386 8a7e43dd Florent Torregrosa
        // Allow other modules to alter the vevent before it's exported.
387
        drupal_alter('date_ical_export_vevent', $vevent, $this->view, $event);
388 85ad3d82 Assos Assos
      }
389 204e4d33 Assos Assos
390 85ad3d82 Assos Assos
      // Now add to the calendar all the timezones used by the events.
391
      foreach ($timezones as $timezone) {
392
        if (strtoupper($timezone) != 'UTC') {
393
          iCalUtilityFunctions::createTimezone($vcalendar, $timezone);
394
        }
395
      }
396 204e4d33 Assos Assos
397 8a7e43dd Florent Torregrosa
      // Allow other modules to alter the vcalendar before it's exported.
398
      drupal_alter('date_ical_export_vcalendar', $vcalendar, $this->view);
399 204e4d33 Assos Assos
400 85ad3d82 Assos Assos
      $output = $vcalendar->createCalendar();
401 8a7e43dd Florent Torregrosa
      // iCalcreator escapes all commas and semicolons in string values, as the
402
      // spec demands. However, some calendar clients are buggy and fail to
403
      // unescape these characters. Users may choose to unescape them here to
404
      // sidestep those clients' bugs.
405
      // NOTE: This results in a non-compliant iCal feed, but it seems like a
406
      // LOT of major clients are bugged this way.
407
      if ($this->_get_option('unescape_punctuation')) {
408
        $output = str_replace('\,', ',', $output);
409
        $output = str_replace('\;', ';', $output);
410
      }
411 204e4d33 Assos Assos
412 85ad3d82 Assos Assos
      // In order to respect the Exclude DTSTAMP option, we unfortunately have
413
      // to parse out the DTSTAMP properties after they get rendered. Simply
414
      // using deleteProperty('DTSTAMP') doesn't work, because iCalcreator
415
      // considers the DTSTAMP to be essential, and will re-create it when
416
      // createCalendar() is called.
417
      if ($this->_get_option('exclude_dtstamp')) {
418
        $filtered_lines = array();
419
        foreach (explode("\r\n", $output) as $line) {
420
          if (strpos($line, 'DTSTAMP') === 0) {
421
            continue;
422
          }
423
          $filtered_lines[] = $line;
424
        }
425
        $output = implode("\r\n", $filtered_lines);
426
      }
427
    }
428 204e4d33 Assos Assos
429 c7b88c87 Assos Assos
    // These steps shouldn't be run during Preview on the View page.
430 85ad3d82 Assos Assos
    if (empty($this->view->live_preview)) {
431
      // Prevent devel module from appending queries to ical export.
432
      $GLOBALS['devel_shutdown'] = FALSE;
433 204e4d33 Assos Assos
434 55670b15 Assos Assos
      drupal_add_http_header('Content-Type', 'text/calendar; charset=UTF-8');
435
      drupal_add_http_header('Cache-Control', 'no-cache, must-revalidate');
436
      drupal_add_http_header('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT');
437 204e4d33 Assos Assos
438 8a7e43dd Florent Torregrosa
      // For sites with Clean URLs disabled, the Display's "path" value ends
439
      // up only in the query args, meaning the filename won't be set properly
440
      // when users download the feed. So we need to manually instruct browsers
441
      // to download a .ics file.
442 85ad3d82 Assos Assos
      if (!variable_get('clean_url', FALSE)) {
443
        $path_array = explode('/', $this->display->display_options['path']);
444 8a7e43dd Florent Torregrosa
        $filename = end($path_array);
445 85ad3d82 Assos Assos
        drupal_add_http_header('Content-Disposition', "attachment; filename=\"$filename\"");
446
      }
447
    }
448 204e4d33 Assos Assos
449 8a7e43dd Florent Torregrosa
    // Allow other modules to alter the rendered calendar.
450
    drupal_alter('date_ical_export_post_render', $output, $this->view);
451 204e4d33 Assos Assos
452 85ad3d82 Assos Assos
    return $output;
453
  }
454
}