Projet

Général

Profil

Paste
Télécharger (14,7 ko) Statistiques
| Branche: | Révision:

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

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