Project

General

Profile

Paste
Download (15.9 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / date_ical / includes / date_ical_plugin_row_ical_entity.inc @ d756b39a

1
<?php
2

    
3
/**
4
 * @file
5
 * Contains the iCal row style plugin.
6
 */
7

    
8
/**
9
 * A Views plugin which builds an iCal VEVENT from a single node.
10
 */
11
class date_ical_plugin_row_ical_entity extends views_plugin_row {
12

    
13
  // Basic properties that let the row style follow relationships.
14
  protected $base_table = 'node';
15
  protected $base_field = 'nid';
16

    
17
  // Stores the nodes loaded with pre_render.
18
  protected $entities = array();
19

    
20
  /**
21
   * Initialize the row plugin.
22
   */
23
  public function init(&$view, &$display, $options = NULL) {
24
    parent::init($view, $display, $options);
25
    $this->base_table = $view->base_table;
26
    $this->base_field = $view->base_field;
27
  }
28

    
29
  /**
30
   * Set up the options for the row plugin.
31
   */
32
  public function option_definition() {
33
    $options = parent::option_definition();
34
    $options['date_field'] = array('default' => array());
35
    $options['summary_field'] = array('default' => array());
36
    $options['location_field'] = array('default' => array());
37
    return $options;
38
  }
39

    
40
  /**
41
   * Build the form for setting the row plugin's options.
42
   */
43
  public function options_form(&$form, &$form_state) {
44
    parent::options_form($form, $form_state);
45

    
46
    // Build the select dropdown for the Date field that the user wants to use
47
    // to populate the date properties in VEVENTs.
48
    $data = date_views_fields($this->base_table);
49
    $options = array();
50
    foreach ($data['name'] as $item => $value) {
51
      // We only want to see one value for each field, so we need to
52
      // skip '_value2' and other columns.
53
      if ($item == $value['fromto'][0]) {
54
        $options[$item] = $value['label'];
55
      }
56
    }
57
    $form['date_field'] = array(
58
      '#type' => 'select',
59
      '#title' => t('Date field'),
60
      '#options' => $options,
61
      '#default_value' => $this->options['date_field'],
62
      '#description' => t('Please identify the field to use as the iCal date for each item in this view.
63
          Add a Date Filter or a Date Argument to the view to limit results to content in a specified date range.'),
64
      '#required' => TRUE,
65
    );
66
    $form['instructions'] = array(
67
      // The surrounding <div> ensures that the settings dialog expands.
68
      '#prefix' => '<div style="font-size: 90%">',
69
      '#suffix' => '</div>',
70
      '#markup' => t("Each item's Title will be the SUMMARY and the rendered iCal view mode will be the DESCRIPTION in the VEVENTs output by this View.
71
        <br>To change the iCal view mode, configure it on the 'Manage Display' page for each Content Type.
72
        Please note that all HTML will be stripped from the output, to comply with iCal standards."),
73
    );
74

    
75
    // Build the select dropdown for the text/node_reference field that the user
76
    // wants to use to (optionally) populate the SUMMARY.
77
    $summary_fields = date_ical_get_summary_fields($this->base_table);
78
    $summary_options = array('default_title' => t('- Default Title -'));
79
    foreach ($summary_fields['name'] as $item => $value) {
80
      $summary_options[$item] = $value['label'];
81
    }
82
    $form['summary_field'] = array(
83
      '#type' => 'select',
84
      '#title' => t('SUMMARY field'),
85
      '#options' => $summary_options,
86
      '#default_value' => $this->options['summary_field'],
87
      '#description' => t('You may optionally change the SUMMARY component for each event in the iCal output.
88
        Choose which text, taxonomy term reference or Node Reference field you would like to be output as the SUMMARY.
89
        If using a Node Reference, the Title of the referenced node will be used.'),
90
    );
91

    
92
    // Build the select dropdown for the text/node_reference field that the user
93
    // wants to use to (optionally) populate the LOCATION.
94
    $location_fields = date_ical_get_location_fields($this->base_table);
95
    $location_options = array('none' => t('- None -'));
96
    foreach ($location_fields['name'] as $item => $value) {
97
      $location_options[$item] = $value['label'];
98
    }
99
    $form['location_field'] = array(
100
      '#type' => 'select',
101
      '#title' => t('LOCATION field'),
102
      '#options' => $location_options,
103
      '#default_value' => $this->options['location_field'],
104
      '#description' => t('You may optionally include a LOCATION component for each event in the iCal output.
105
        Choose which text or Node Reference field you would like to be output as the LOCATION.
106
        If using a Node Reference, the Title of the referenced node will be used.'),
107
    );
108
  }
109

    
110
  /**
111
   * Preload the list of entities which will appear in the view.
112
   *
113
   * TODO: When the date is coming in through a relationship, the nid
114
   * of the view is not the right node to use: we need the related node.
115
   * Need to sort out how that should be handled.
116
   */
117
  public function pre_render($values) {
118
    // Preload each entity used in this view from the cache.
119
    // Provides all the entity values relatively cheaply, and we don't
120
    // need to do it repeatedly for the same entity if there are
121
    // multiple results for one entity.
122
    $ids = array();
123
    foreach ($values as $row) {
124
      // Use the $id as the key so we create only value per entity.
125
      $id = $row->{$this->field_alias};
126

    
127
      // Node revisions need special loading.
128
      if ($this->view->base_table == 'node_revision') {
129
        $this->entities[$id] = node_load(NULL, $id);
130
      }
131
      // For other entities we just create an array of ids to pass
132
      // to entity_load().
133
      else {
134
        $ids[$id] = $id;
135
      }
136
    }
137

    
138
    $base_tables = date_views_base_tables();
139
    $this->entity_type = $base_tables[$this->view->base_table];
140
    if (!empty($ids)) {
141
      $this->entities = entity_load($this->entity_type, $ids);
142
    }
143

    
144
    // Get the language for this view.
145
    $this->language = $this->display->handler->get_option('field_language');
146
    $substitutions = views_views_query_substitutions($this->view);
147
    if (array_key_exists($this->language, $substitutions)) {
148
      $this->language = $substitutions[$this->language];
149
    }
150
  }
151

    
152
  /**
153
   * Renders the entities returned by the view into event arrays.
154
   */
155
  public function render($row) {
156
    $id = $row->{$this->field_alias};
157
    if (!is_numeric($id)) {
158
      return NULL;
159
    }
160

    
161
    // Load the specified entity:
162
    $entity = $this->entities[$id];
163
    if (empty($entity)) {
164
      // This can happen when an RRULE is involved.
165
      return NULL;
166
    }
167

    
168
    $date_fields = date_views_fields($this->base_table);
169
    $date_info = $date_fields['name'][$this->options['date_field']];
170
    $field_name  = str_replace(array('_value', '_value2'), '', $date_info['real_field_name']);
171
    $delta_field = $date_info['delta_field'];
172
    $is_field    = $date_info['is_field'];
173

    
174
    // Sometimes the timestamp is actually the revision timestamp.
175
    if ($this->view->base_table == 'node_revision' && $field_name == 'timestamp') {
176
      $field_name = 'revision_timestamp';
177
    }
178

    
179
    if (!isset($entity->$field_name)) {
180
      // This entity doesn't have the date property that the user configured
181
      // our view to use. We can't do anything with it.
182
      return NULL;
183
    }
184
    $date_field = $entity->$field_name;
185

    
186
    // Pull the date value from the specified field of the entity.
187
    $entity->date_id = array();
188
    $start = NULL;
189
    $end   = NULL;
190
    $delta = isset($row->$delta_field) ? $row->$delta_field : 0;
191
    if ($is_field) {
192
      $items = field_get_items($this->entity_type, $entity, $field_name);
193
      if (!$items) {
194
        // This entity doesn't have data in the date field that the user
195
        // configured our view to use. We can't do anything with it.
196
        return;
197
      }
198
      $date_field = $items[$delta];
199
      global $base_url;
200
      $domain = preg_replace('#^https?://#', '', $base_url);
201
      $entity->date_id[] = "calendar.$id.$field_name.$delta@$domain";
202

    
203
      if (!empty($date_field['value'])) {
204
        $start = new DateObject($date_field['value'], $date_field['timezone_db']);
205
        if (!empty($date_field['value2'])) {
206
          $end = new DateObject($date_field['value2'], $date_field['timezone_db']);
207
        }
208
        else {
209
          $end = clone $start;
210
        }
211
      }
212
    }
213
    elseif (!$is_field && !empty($date_field)) {
214
      $start = new DateObject($date_field, $date_field['timezone_db']);
215
      $end   = new DateObject($date_field, $date_field['timezone_db']);
216
    }
217

    
218
    // Set the item date to the proper display timezone.
219
    $start->setTimezone(new DateTimeZone($date_field['timezone']));
220
    $end->setTimezone(new DateTimeZone($date_field['timezone']));
221

    
222
    // Check if the start and end dates indicate that this is an All Day event.
223
    $all_day = date_is_all_day(
224
      date_format($start, DATE_FORMAT_DATETIME),
225
      date_format($end, DATE_FORMAT_DATETIME),
226
      date_granularity_precision($date_info['granularity'])
227
    );
228

    
229
    if ($all_day) {
230
      // According to RFC 2445 (clarified in RFC 5545) the DTEND value is
231
      // non-inclusive. When dealing with All Day values, they're DATEs rather
232
      // than DATETIMEs, so we need to add a day to conform to RFC.
233
      $end->modify("+1 day");
234
    }
235

    
236
    // If the user specified a LOCATION field, pull that data from the entity.
237
    $location = '';
238
    if (!empty($this->options['location_field']) && $this->options['location_field'] != 'none') {
239
      $location_fields = date_ical_get_location_fields($this->base_table);
240
      $location_info = $location_fields['name'][$this->options['location_field']];
241
      $location_field_name = $location_info['real_field_name'];
242

    
243
      // Only attempt this is the entity actually has this field.
244
      $items = field_get_items($this->entity_type, $entity, $location_field_name);
245
      if ($items) {
246
        $location_field = $items[0];
247
        if ($location_info['type'] == 'node_reference') {
248
          // Make sure this Node Reference actually references a node.
249
          if ($location_field['nid']) {
250
            $node = node_load($location_field['nid']);
251
            $location = $node->title;
252
          }
253
        }
254
        elseif ($location_info['type'] == 'addressfield') {
255
          $locations = array();
256
          // Get full country name
257
          if (!empty($location_field['country'])) {
258
            require_once DRUPAL_ROOT . '/includes/locale.inc';
259
            $countries = country_get_list();
260
            $location_field['country'] = $countries[$location_field['country']];
261
          }
262
          foreach ($location_field as $key => $loc) {
263
            if ($loc && !in_array($key, array('first_name', 'last_name'))) {
264
              $locations[] = $loc;
265
            }
266
          }
267
          $location = implode(', ', array_reverse($locations));
268
        }
269
        elseif ($location_info['type'] == 'location') {
270
          $included_fields = array(
271
            'name',
272
            'additional',
273
            'street',
274
            'city',
275
            'province_name',
276
            'postal_code',
277
            'country_name'
278
          );
279
          $location_data = array();
280
          foreach ($included_fields as $included_field) {
281
            if (!empty($location_field[$included_field])) {
282
              $location_data[] = $location_field[$included_field];
283
            }
284
          }
285
          $location = implode(', ', $location_data);
286
        }
287
        elseif ($location_info['type'] == 'node_reference') {
288
        // Make sure this Node Reference actually references a node.
289
          if ($location_field['nid']) {
290
            $node = node_load($location_field['nid']);
291
            $location = $node->title;
292
          }
293
        }
294
        elseif ($location_info['type'] == 'taxonomy_term_reference') {
295
          $terms = taxonomy_term_load_multiple(array_column($items,'tid'));
296
          // Make sure that there are terms that were loaded.
297
          if ($terms) {
298
            $term_names = array();
299
            foreach ($terms as $term) {
300
              $term_names[] = $term->name;
301
            }
302
            $location = implode(', ', $term_names);
303
          }
304
        }
305
        else {
306
          $location = $location_field['value'];
307
        }
308
      }
309
    }
310

    
311
    // Create the rendered event using the display settings from the
312
    // iCal view mode.
313
    $rendered_array = entity_view($this->entity_type, array($entity), 'ical', $this->language, TRUE);
314
    $data = array(
315
      'description' => drupal_render($rendered_array),
316
      'summary' => entity_label($this->entity_type, $entity),
317
    );
318
    if (!empty($this->options['summary_field']) && $this->options['summary_field'] != 'default_title') {
319
      $summary_fields = date_ical_get_summary_fields();
320
      $summary_info = $summary_fields['name'][$this->options['summary_field']];
321
      $summary_field_name = $summary_info['real_field_name'];
322
      // Only attempt this is the entity actually has this field.
323
      $items = field_get_items($this->entity_type, $entity, $summary_field_name);
324
      $summary = '';
325
      if ($items) {
326
        $summary_field = $items[0];
327
        if ($summary_info['type'] == 'node_reference') {
328
          // Make sure this Node Reference actually references a node.
329
          if ($summary_field['nid']) {
330
            $node = node_load($summary_field['nid']);
331
            $summary = $node->title;
332
          }
333
        }
334
        elseif ($summary_info['type'] == 'taxonomy_term_reference') {
335
          $terms = taxonomy_term_load_multiple(array_column($items,'tid'));
336
          // Make sure that there are terms that were loaded.
337
          if ($terms) {
338
            $term_names = array();
339
            foreach ($terms as $term) {
340
              $term_names[] = $term->name;
341
            }
342
            $summary = implode(', ', $term_names);
343
          }
344
        }
345
        else {
346
          $summary = trim($summary_field['value']);
347
        }
348
        $data['summary'] = $summary ? $summary : $data['summary'];
349
      }
350
    }
351
    // Allow other modules to alter the HTML of the Summary and Description,
352
    // before it gets converted to iCal-compliant plaintext. This allows users
353
    // to set up a newline between fields, for instance.
354
    $context = array(
355
      'entity' => $entity,
356
      'entity_type' => $this->entity_type,
357
      'language' => $this->language,
358
    );
359
    drupal_alter('date_ical_export_html', $data, $this->view, $context);
360

    
361
    $event = array();
362
    $event['summary'] = date_ical_sanitize_text($data['summary']);
363
    $event['description'] = date_ical_sanitize_text($data['description']);
364
    $event['all_day'] = $all_day;
365
    $event['start'] = $start;
366
    $event['end'] = $end;
367
    $uri = entity_uri($this->entity_type, $entity);
368
    $uri['options']['absolute'] = TRUE;
369
    $event['url'] = url($uri['path'], $uri['options']);
370
    if ($is_field && !empty($date_field['rrule'])) {
371
      $event['rrule'] = $date_field['rrule'];
372
    }
373
    if ($location) {
374
      $event['location'] = date_ical_sanitize_text($location);
375
    }
376

    
377
    // For this event's UID, use either the date_id generated by the Date
378
    // module, or the event page's URL if the date_id isn't available.
379
    $event['uid'] = !empty($entity->date_id) ? $entity->date_id[0] : $event['url'];
380

    
381
    // If we are using a repeat rule (and not just multi-day events) we
382
    // remove the item from the entities list so that its VEVENT won't be
383
    // re-created.
384
    if (!empty($event['rrule'])) {
385
      $this->entities[$id] = NULL;
386
    }
387

    
388
    // According to the iCal standard, CREATED and LAST-MODIFIED must be UTC.
389
    // Fortunately, Drupal stores timestamps in the DB as UTC, so we just need
390
    // to tell DateObject to treat the timestamp as UTC from the start.
391
    if (isset($entity->created)) {
392
      $event['created'] = new DateObject($entity->created, 'UTC');
393
    }
394
    // Pull the 'changed' date from the entity (if available), so that
395
    // subscription clients can tell if the event has been updated.
396
    if (isset($entity->changed)) {
397
      $event['last-modified'] = new DateObject($entity->changed, 'UTC');
398
    }
399
    elseif (isset($entity->created)) {
400
      // If changed is unset, but created is, use that for last-modified.
401
      $event['last-modified'] = new DateObject($entity->created, 'UTC');
402
    }
403

    
404
    // Allow other modules to alter the structured event object, before it gets
405
    // passed to the style plugin to be converted into an iCalcreator vevent.
406
    drupal_alter('date_ical_export_raw_event', $event, $this->view, $context);
407

    
408
    return $event;
409
  }
410
}