Projet

Général

Profil

Paste
Télécharger (16,2 ko) Statistiques
| Branche: | Révision:

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

1 85ad3d82 Assos Assos
<?php
2
/**
3
 * @file
4
 * Defines the iCal Fields row style plugin, which lets users map view fields
5
 * to the components of the VEVENTs in the iCal feed.
6
 */
7
8
/**
9 8a7e43dd Florent Torregrosa
 * A Views plugin which builds an iCal VEVENT from a views row with Fields.
10 85ad3d82 Assos Assos
 */
11
class date_ical_plugin_row_ical_fields extends views_plugin_row {
12 204e4d33 Assos Assos
13 8a7e43dd Florent Torregrosa
  /**
14
   * Set up the options for the row plugin.
15
   */
16
  public function option_definition() {
17 85ad3d82 Assos Assos
    $options = parent::option_definition();
18
    $options['date_field'] = array('default' => '');
19
    $options['title_field'] = array('default' => '');
20
    $options['description_field'] = array('default' => '');
21
    $options['location_field'] = array('default' => '');
22 62e0cc08 Assos Assos
    $options['categories_field'] = array('default' => '');
23 85ad3d82 Assos Assos
    $options['additional_settings']['skip_blank_dates'] = array('default' => FALSE);
24
    return $options;
25
  }
26 204e4d33 Assos Assos
27 8a7e43dd Florent Torregrosa
  /**
28
   * Build the form for setting the row plugin's options.
29
   */
30
  public function options_form(&$form, &$form_state) {
31 85ad3d82 Assos Assos
    parent::options_form($form, $form_state);
32
    $all_field_labels = $this->display->handler->get_field_labels();
33
    $date_field_labels = $this->get_date_field_candidates($all_field_labels);
34 8a7e43dd Florent Torregrosa
    $date_field_label_options = array_merge(array('first_available' => t('First populated Date field')), $date_field_labels);
35
    $text_field_label_options = array_merge(array('' => t('- None -')), $all_field_labels);
36 204e4d33 Assos Assos
37 85ad3d82 Assos Assos
    $form['instructions'] = array(
38 8a7e43dd Florent Torregrosa
      // The surrounding <div> ensures that the settings dialog expands.
39 85ad3d82 Assos Assos
      '#prefix' => '<div style="font-size: 90%">',
40
      '#suffix' => '</div>',
41 8a7e43dd Florent Torregrosa
      '#markup' => t("Once you've finished setting up the fields for this View, you may want to return to this dialog to set the Date field."),
42 85ad3d82 Assos Assos
    );
43
    $form['date_field'] = array(
44
      '#type' => 'select',
45
      '#title' => t('Date field'),
46 8a7e43dd Florent Torregrosa
      '#description' => t('The views field to use as the start (and possibly end) time for each event (DTSTART/DTEND).
47
        If you retain the default ("First populated Date field"), Date iCal will use the first non-empty Date field in the row.'),
48 85ad3d82 Assos Assos
      '#options' => $date_field_label_options,
49
      '#default_value' => $this->options['date_field'],
50
      '#required' => TRUE,
51
    );
52
    $form['title_field'] = array(
53
      '#type' => 'select',
54
      '#title' => t('Title field'),
55
      '#description' => t('The views field to use as the title for each event (SUMMARY).'),
56
      '#options' => $text_field_label_options,
57
      '#default_value' => $this->options['title_field'],
58
      '#required' => FALSE,
59
    );
60
    $form['description_field'] = array(
61
      '#type' => 'select',
62
      '#title' => t('Description field'),
63
      '#description' => t("The views field to use as the body text for each event (DESCRIPTION).<br>
64
          If you wish to include more than one entity field in the event body, you may want to use the 'Content: Rendered Node' views field,
65
          and set it to the 'iCal' view mode. Then configure the iCal view mode on your event nodes to include the text you want."),
66
      '#options' => $text_field_label_options,
67
      '#default_value' => $this->options['description_field'],
68
      '#required' => FALSE,
69
    );
70
    $form['location_field'] = array(
71
      '#type' => 'select',
72
      '#title' => t('Location field'),
73
      '#description' => t('(optional) The views field to use as the location for each event (LOCATION).'),
74
      '#options' => $text_field_label_options,
75
      '#default_value' => $this->options['location_field'],
76
      '#required' => FALSE,
77
    );
78 62e0cc08 Assos Assos
    $form['categories_field'] = array(
79
      '#type' => 'select',
80
      '#title' => t('Categories field'),
81
      '#description' => t('(optional) The views field to use as the categories for each event (CATEGORIES).'),
82
      '#options' => $text_field_label_options,
83
      '#default_value' => $this->options['categories_field'],
84
      '#required' => FALSE,
85
    );
86 85ad3d82 Assos Assos
    $form['additional_settings'] = array(
87
      '#type' => 'fieldset',
88
      '#title' => t('Additional settings'),
89
      '#collapsible' => FALSE,
90
      '#collapsed' => FALSE,
91
    );
92
    $form['additional_settings']['skip_blank_dates'] = array(
93
      '#type' => 'checkbox',
94
      '#title' => t('Skip blank dates'),
95
      '#description' => t('Normally, if a view result has a blank date field, the feed will display an error,
96
        because it is impossible to create an iCal event with no date. This option makes Views silently skip those results, instead.'),
97
      '#default_value' => $this->options['additional_settings']['skip_blank_dates'],
98
    );
99
  }
100 204e4d33 Assos Assos
101 8a7e43dd Florent Torregrosa
  /**
102
   * Set up the environment for the render() function.
103
   */
104
  public function pre_render($result) {
105 85ad3d82 Assos Assos
    // Get the language for this view.
106
    $this->language = $this->display->handler->get_option('field_language');
107
    $substitutions = views_views_query_substitutions($this->view);
108
    if (array_key_exists($this->language, $substitutions)) {
109
      $this->language = $substitutions[$this->language];
110
    }
111
    $this->repeated_dates = array();
112
  }
113 204e4d33 Assos Assos
114 85ad3d82 Assos Assos
  /**
115 8a7e43dd Florent Torregrosa
   * Returns an Event array row in the query with index: $row->index.
116 85ad3d82 Assos Assos
   */
117 8a7e43dd Florent Torregrosa
  public function render($row) {
118 85ad3d82 Assos Assos
    $date_field_name = $this->options['date_field'];
119 204e4d33 Assos Assos
120 8a7e43dd Florent Torregrosa
    // If this view is set to use the first populated date field, check each
121
    // field in the row to find the first non-NULL Date field.
122
    if ($date_field_name == 'first_available') {
123
      foreach (get_object_vars($row) as $name => $value) {
124
        if (strpos($name, 'field_field') === 0) {
125
          // This property's name starts with "field_field", which means it's
126
          // the actual field data for a field in this view.
127
          if (!empty($value[0]['raw']['date_type'])) {
128
            // Cut off the first "field_" from $name to get the field name.
129
            $date_field_name = substr($name, 6);
130
            break;
131
          }
132
        }
133
      }
134
    }
135 204e4d33 Assos Assos
136 85ad3d82 Assos Assos
    // Fetch the event's date information.
137
    try {
138 8a7e43dd Florent Torregrosa
      if ($date_field_name == 'first_available') {
139
        // If $date_field_name is still 'first_available' at this point, we
140
        // couldn't find an available Date value. Processing cannot proceed.
141
        $title = strip_tags($this->view->style_plugin->get_field($row->index, $this->options['title_field']));
142
        if (empty($title)) {
143
          $title = "Undetermined title";
144
        }
145 62e0cc08 Assos Assos
        throw new BlankDateFieldException(
146
          t('The row titled "@title" has no available Date value. An iCal entry cannot be created for it.',
147
              array('@title' => $title)
148
          )
149
        );
150 8a7e43dd Florent Torregrosa
      }
151
      $date = $this->get_row_date($row, $date_field_name);
152 85ad3d82 Assos Assos
    }
153
    catch (BlankDateFieldException $e) {
154 8a7e43dd Florent Torregrosa
      // Unless the user has specifically said that they want to skip rows
155
      // with blank dates, let this exception percolate.
156 85ad3d82 Assos Assos
      if ($this->options['additional_settings']['skip_blank_dates']) {
157
        return NULL;
158
      }
159
      else {
160
        throw $e;
161
      }
162
    }
163 204e4d33 Assos Assos
164 85ad3d82 Assos Assos
    // Create the event by starting with the date array from this row.
165
    $event = $date;
166 204e4d33 Assos Assos
167 8a7e43dd Florent Torregrosa
    $entity = $row->_field_data[$this->view->base_field]['entity'];
168
    $entity_type = $row->_field_data[$this->view->base_field]['entity_type'];
169 85ad3d82 Assos Assos
    // Add the CREATED, LAST-MODIFIED, and URL components based on the entity.
170
    // According to the iCal standard, CREATED and LAST-MODIFIED must be UTC.
171
    // Fortunately, Drupal stores timestamps in the DB as UTC, so we just need
172
    // to specify that UTC be used rather than the server's local timezone.
173
    if (isset($entity->created)) {
174 8a7e43dd Florent Torregrosa
      $event['created'] = new DateObject($entity->created, 'UTC');
175 85ad3d82 Assos Assos
    }
176
    if (isset($entity->changed)) {
177 8a7e43dd Florent Torregrosa
      $event['last-modified'] = new DateObject($entity->changed, 'UTC');
178 85ad3d82 Assos Assos
    }
179 8a7e43dd Florent Torregrosa
    elseif (isset($entity->created)) {
180 85ad3d82 Assos Assos
      // If changed is unset, but created is, use that for last-modified.
181 8a7e43dd Florent Torregrosa
      $event['last-modified'] = new DateObject($entity->created, 'UTC');
182 85ad3d82 Assos Assos
    }
183
    $uri = entity_uri($entity_type, $entity);
184
    $uri['options']['absolute'] = TRUE;
185
    $event['url'] = url($uri['path'], $uri['options']);
186 204e4d33 Assos Assos
187 85ad3d82 Assos Assos
    // Generate a unique ID for this event by emulating the way the Date module
188
    // creates a Date ID.
189 62e0cc08 Assos Assos
    $is_relationaship = FALSE;
190
    $date_field_delta = 0;
191 85ad3d82 Assos Assos
    if (isset($row->{"field_data_{$date_field_name}_delta"})) {
192
      $date_field_delta = $row->{"field_data_{$date_field_name}_delta"};
193
    }
194 62e0cc08 Assos Assos
    else if (!empty($entity->{$date_field_name})) {
195 85ad3d82 Assos Assos
      // I'm not sure why the "field_data_{field_name}_delta" field is part of
196
      // the $row, so it's possible that it will sometimes be missing. If it
197
      // is, make an educated guess about the delta by comparing this row's
198
      // start date to each of the entity's dates.
199
      foreach ($entity->{$date_field_name}['und'] as $ndx => $date_array) {
200
        if ($date['start']->originalTime == $date_array['value']) {
201
          $date_field_delta = $ndx;
202
          break;
203
        }
204
      }
205
    }
206 62e0cc08 Assos Assos
    else if (!empty($this->display->display_options['fields'][$date_field_name]['relationship'])) {
207
      // This block covers another potential situation where
208
      // "field_data_{field_name}_delta" is missing: dates gathered through a
209
      // relationship. We retrieve the name of the relationship through which
210
      // the date field is being gathered, and the delta of that relationship,
211
      // in case it's multi-delta.
212
      $rel_name = $this->display->display_options['fields'][$date_field_name]['relationship'];
213
      $rel_delta = $row->index;
214
      $is_relationaship = TRUE;
215
    }
216
    else {
217
      // We couldn't obtain enough info to determine the real $date_field_delta.
218
      // So we just leave it as 0. Hopefully this won't cause problems.
219
    }
220 85ad3d82 Assos Assos
    $entity_id = $row->{$this->view->base_field};
221 8a7e43dd Florent Torregrosa
    global $base_url;
222
    $domain = preg_replace('#^https?://#', '', $base_url);
223 62e0cc08 Assos Assos
    if (!$is_relationaship) {
224
      $event['uid'] = "calendar.$entity_id.$date_field_name.$date_field_delta@$domain";
225
    }
226
    else {
227
      $event['uid'] = "calendar.$entity_id.$rel_name.$rel_delta.$date_field_name.$date_field_delta@$domain";
228
    }
229 204e4d33 Assos Assos
230 85ad3d82 Assos Assos
    // Because of the way that Date implements repeating dates, we're going to
231
    // be given a separate view result for each repeat. We only want to
232
    // render a VEVENT (with an RRULE) for the first instance of that date, so
233
    // we need to record the entity ID and field name for each result that has
234
    // an RRULE, then skip any that we've already seen.
235
    if (!empty($date['rrule'])) {
236
      $repeat_id = "$entity_id.$date_field_name";
237
      if (!isset($this->repeated_dates[$repeat_id])) {
238
        $this->repeated_dates[$repeat_id] = $repeat_id;
239
      }
240
      else {
241
        return FALSE;
242
      }
243
    }
244 204e4d33 Assos Assos
245 85ad3d82 Assos Assos
    // Retrieve the rendered text fields.
246 8a7e43dd Florent Torregrosa
    $text_fields['summary'] = $this->get_field($row->index, $this->options['title_field']);
247
    $text_fields['description'] = $this->get_field($row->index, $this->options['description_field']);
248
    $text_fields['location'] = $this->get_field($row->index, $this->options['location_field']);
249 62e0cc08 Assos Assos
    $text_fields['categories'] = $this->get_field($row->index, $this->options['categories_field']);
250 204e4d33 Assos Assos
251 85ad3d82 Assos Assos
    // Allow other modules to alter the rendered text fields before they get
252
    // sanitized for iCal-compliance. This is most useful for fields of type
253
    // "Content: Rendered Node", which are likely to have complex HTML.
254
    $context = array(
255
      'row' => $row,
256 8a7e43dd Florent Torregrosa
      'row_index' => $row->index,
257 85ad3d82 Assos Assos
      'language' => $this->language,
258 8a7e43dd Florent Torregrosa
      'options' => $this->options,
259 85ad3d82 Assos Assos
    );
260 8a7e43dd Florent Torregrosa
    drupal_alter('date_ical_export_html', $text_fields, $this->view, $context);
261 204e4d33 Assos Assos
262 85ad3d82 Assos Assos
    // Sanitize the text fields for iCal compliance, and add them to the event.
263 ca0757b9 Assos Assos
    $event['summary'] = date_ical_sanitize_text($text_fields['summary']);
264
    $event['location'] = date_ical_sanitize_text($text_fields['location']);
265 85ad3d82 Assos Assos
    $event['description'] = date_ical_sanitize_text($text_fields['description']);
266 62e0cc08 Assos Assos
    $event['categories'] = date_ical_sanitize_text($text_fields['categories']);
267 204e4d33 Assos Assos
268 ca0757b9 Assos Assos
    // Allow other modules to alter the event object before it gets passed to
269 85ad3d82 Assos Assos
    // the style plugin to be converted into an iCal VEVENT.
270 8a7e43dd Florent Torregrosa
    drupal_alter('date_ical_export_raw_event', $event, $this->view, $context);
271 204e4d33 Assos Assos
272 85ad3d82 Assos Assos
    return $event;
273
  }
274
275
  /**
276
   * Returns an normalized array for the current row's datefield/timestamp.
277
   *
278
   * @param object $row
279
   *   The current row object.
280 8a7e43dd Florent Torregrosa
   * @param string $date_field_name
281
   *   The name of the date field.
282 85ad3d82 Assos Assos
   *
283
   * @return array
284
   *   The normalized array.
285 8a7e43dd Florent Torregrosa
   */
286
  protected function get_row_date($row, $date_field_name) {
287 85ad3d82 Assos Assos
    $start = NULL;
288
    $end   = NULL;
289
    $rrule = NULL;
290
    $delta = 0;
291
    $is_date_field = FALSE;
292 204e4d33 Assos Assos
293 85ad3d82 Assos Assos
    // Fetch the date field value.
294 8a7e43dd Florent Torregrosa
    $date_field_value = $this->view->style_plugin->get_field_value($row->index, $date_field_name);
295 204e4d33 Assos Assos
296 85ad3d82 Assos Assos
    // Handle date fields.
297
    if (isset($date_field_value[$delta]) && is_array($date_field_value[$delta])) {
298
      $is_date_field = TRUE;
299
      $date_field = $date_field_value[$delta];
300 204e4d33 Assos Assos
301 85ad3d82 Assos Assos
      $start = new DateObject($date_field['value'], $date_field['timezone_db']);
302 8a7e43dd Florent Torregrosa
      if (!empty($date_field['value2'])) {
303 85ad3d82 Assos Assos
        $end = new DateObject($date_field['value2'], $date_field['timezone_db']);
304
      }
305
      else {
306
        $end = clone $start;
307
      }
308 204e4d33 Assos Assos
309 85ad3d82 Assos Assos
      if (isset($date_field['rrule'])) {
310
        $rrule = $date_field['rrule'];
311
      }
312
    }
313
    elseif (is_numeric($date_field_value)) {
314 8a7e43dd Florent Torregrosa
      // Handle timestamps, which are always in UTC.
315 85ad3d82 Assos Assos
      $start = new DateObject($date_field_value, 'UTC');
316
      $end   = new DateObject($date_field_value, 'UTC');
317
    }
318
    else {
319 8a7e43dd Florent Torregrosa
      // Processing cannot proceed with a blank date value.
320
      $title = strip_tags($this->view->style_plugin->get_field($row->index, $this->options['title_field']));
321
      throw new BlankDateFieldException(t("The row %title has a blank date. An iCal entry cannot be created for it.", array('%title' => $title)));
322 85ad3d82 Assos Assos
    }
323 204e4d33 Assos Assos
324 85ad3d82 Assos Assos
    // Set the display timezone to whichever tz is stored for this field.
325
    // If there isn't a stored TZ, use the site default.
326
    $timezone = isset($date_field['timezone']) ? $date_field['timezone'] : date_default_timezone(FALSE);
327
    $dtz = new DateTimeZone($timezone);
328
    $start->setTimezone($dtz);
329
    $end->setTimezone($dtz);
330 204e4d33 Assos Assos
331 85ad3d82 Assos Assos
    $granularity = 'second';
332
    if ($is_date_field) {
333 8a7e43dd Florent Torregrosa
      $granularity_settings = $this->view->field[$date_field_name]->field_info['settings']['granularity'];
334 85ad3d82 Assos Assos
      $granularity = date_granularity_precision($granularity_settings);
335
    }
336 204e4d33 Assos Assos
337 85ad3d82 Assos Assos
    // Check if the start and end dates indicate that this is an All Day event.
338
    $all_day = date_is_all_day(
339
      date_format($start, DATE_FORMAT_DATETIME),
340
      date_format($end, DATE_FORMAT_DATETIME),
341
      $granularity
342
    );
343 204e4d33 Assos Assos
344 85ad3d82 Assos Assos
    if ($all_day) {
345
      // According to RFC 2445 (clarified in RFC 5545) the DTEND value is
346
      // non-inclusive. When dealing with All Day values, they are DATEs rather
347
      // than DATETIMEs, so we need to add a day to conform to RFC.
348
      $end->modify("+1 day");
349
    }
350 204e4d33 Assos Assos
351 85ad3d82 Assos Assos
    $date = array(
352
      'start' => $start,
353
      'end' => $end,
354
      'all_day' => $all_day,
355
      'rrule' => $rrule,
356
    );
357 204e4d33 Assos Assos
358 85ad3d82 Assos Assos
    return $date;
359
  }
360
361
  /**
362 8a7e43dd Florent Torregrosa
   * Filter the list of views fields down to only supported date-type fields.
363
   *
364
   * The supported date-type fields are timestamps and the three Date fields.
365 85ad3d82 Assos Assos
   *
366
   * @param array $view_fields
367 8a7e43dd Florent Torregrosa
   *   An associative array like views_plugin_display::get_field_labels().
368 85ad3d82 Assos Assos
   *
369
   * @return array
370 8a7e43dd Florent Torregrosa
   *   An associative array (alias => label) of date fields.
371 85ad3d82 Assos Assos
   */
372 8a7e43dd Florent Torregrosa
  protected function get_date_field_candidates($view_fields) {
373 85ad3d82 Assos Assos
    $handlers = $this->display->handler->get_handlers('field');
374
    $field_candidates = array();
375 8a7e43dd Florent Torregrosa
    // These are Date, Date (ISO format), and Date (Unix timestamp).
376
    $date_fields = array('datetime', 'date', 'datestamp');
377 204e4d33 Assos Assos
378 85ad3d82 Assos Assos
    foreach ($view_fields as $alias => $label) {
379 8a7e43dd Florent Torregrosa
      $handler_class = get_class($handlers[$alias]);
380
      if ($handler_class == 'views_handler_field_date'
381
          || ($handler_class == 'views_handler_field_field'
382
            && in_array($handlers[$alias]->field_info['type'], $date_fields))) {
383 85ad3d82 Assos Assos
        $field_candidates[$alias] = $label;
384
      }
385
    }
386
    return $field_candidates;
387
  }
388
389
  /**
390
   * Retrieves a field value from the style plugin.
391
   *
392
   * @param int $index
393 8a7e43dd Florent Torregrosa
   *   The index count of the row
394 85ad3d82 Assos Assos
   * @param string $field_id
395
   *   The ID assigned to the required field in the display.
396 8a7e43dd Florent Torregrosa
   *
397
   * @see views_plugin_style::get_field()
398 85ad3d82 Assos Assos
   */
399 8a7e43dd Florent Torregrosa
  protected function get_field($index, $field_id) {
400 85ad3d82 Assos Assos
    if (empty($this->view->style_plugin) || !is_object($this->view->style_plugin) || empty($field_id)) {
401
      return '';
402
    }
403
    return $this->view->style_plugin->get_field($index, $field_id);
404
  }
405
}