date_ical_plugin_row_ical_entity.inc 15.1 KB
Newer Older
Karen Stevenson's avatar
Karen Stevenson committed
1
<?php
2

Karen Stevenson's avatar
Karen Stevenson committed
3 4 5 6 7 8
/**
 * @file
 * Contains the iCal row style plugin.
 */

/**
9
 * A Views plugin which builds an iCal VEVENT from a single node.
Karen Stevenson's avatar
Karen Stevenson committed
10
 */
Robert Rollins's avatar
Robert Rollins committed
11
class date_ical_plugin_row_ical_entity extends views_plugin_row {
Robert Rollins's avatar
Robert Rollins committed
12

Karen Stevenson's avatar
Karen Stevenson committed
13
  // Basic properties that let the row style follow relationships.
14 15
  protected $base_table = 'node';
  protected $base_field = 'nid';
Robert Rollins's avatar
Robert Rollins committed
16

Karen Stevenson's avatar
Karen Stevenson committed
17
  // Stores the nodes loaded with pre_render.
18
  protected $entities = array();
Robert Rollins's avatar
Robert Rollins committed
19

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

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

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

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

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

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

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

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

Karen Stevenson's avatar
Karen Stevenson committed
138 139 140 141 142
    $base_tables = date_views_base_tables();
    $this->entity_type = $base_tables[$this->view->base_table];
    if (!empty($ids)) {
      $this->entities = entity_load($this->entity_type, $ids);
    }
Robert Rollins's avatar
Robert Rollins committed
143

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

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

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

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

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

179 180
    if (!isset($entity->$field_name)) {
      // 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;
Robert Rollins's avatar
Robert Rollins committed
185

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

203 204
      if (!empty($date_field['value'])) {
        $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']);
Karen Stevenson's avatar
Karen Stevenson committed
207 208 209 210 211 212
        }
        else {
          $end = clone $start;
        }
      }
    }
213 214 215
    elseif (!$is_field && !empty($date_field)) {
      $start = new DateObject($date_field, $date_field['timezone_db']);
      $end   = new DateObject($date_field, $date_field['timezone_db']);
Karen Stevenson's avatar
Karen Stevenson committed
216
    }
Robert Rollins's avatar
Robert Rollins committed
217

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

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

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

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

243
      // Only attempt this is the entity actually has this field.
244 245
      $items = field_get_items($this->entity_type, $entity, $location_field_name);
      if ($items) {
246
        $location_field = $items[0];
247
        if ($location_info['type'] == 'node_reference') {
248 249 250
          // Make sure this Node Reference actually references a node.
          if ($location_field['nid']) {
            $node = node_load($location_field['nid']);
251
            $location = $node->title;
252 253
          }
        }
254 255
        elseif ($location_info['type'] == 'addressfield') {
          $locations = array();
256 257 258
          // Concatenate street and house number
          $location_field['thoroughfare'] = implode(' ', array($location_field['thoroughfare'], $location_field['premise']));
          unset($location_field['premise']);
259
          foreach ($location_field as $key => $loc) {
260 261 262 263 264 265
            if ($loc && !in_array($key, array('first_name', 'last_name'))) {
              $locations[] = $loc;
            }
          }
          $location = implode(', ', array_reverse($locations));
        }
266 267 268 269 270 271 272 273 274 275 276 277 278
        elseif ($location_info['type'] == 'location') {
          $included_fields = array(
            'name',
            'additional',
            'street',
            'city',
            'province_name',
            'postal_code',
            'country_name'
          );
          $location_data = array();
          foreach ($included_fields as $included_field) {
            if (!empty($location_field[$included_field])) {
279
              $location_data[] = $location_field[$included_field];
280 281
            }
          }
282
          $location = implode(', ', $location_data);
283
        }
284
        else {
285
          $location = $location_field['value'];
286 287 288
        }
      }
    }
Robert Rollins's avatar
Robert Rollins committed
289

290 291
    // Create the rendered event using the display settings from the
    // iCal view mode.
Karen Stevenson's avatar
Karen Stevenson committed
292
    $rendered_array = entity_view($this->entity_type, array($entity), 'ical', $this->language, TRUE);
293 294
    $data = array(
      'description' => drupal_render($rendered_array),
295
      'summary' => entity_label($this->entity_type, $entity),
296
    );
297 298 299 300 301 302 303 304
    if (!empty($this->options['summary_field']) && $this->options['summary_field'] != 'default_title') {
      $summary_fields = date_ical_get_summary_fields();
      $summary_info = $summary_fields['name'][$this->options['summary_field']];
      $summary_field_name = $summary_info['real_field_name'];
      // Only attempt this is the entity actually has this field.
      $items = field_get_items($this->entity_type, $entity, $summary_field_name);
      $summary = '';
      if ($items) {
305
        $summary_field = $items[0];
306 307 308 309
        if ($summary_info['type'] == 'node_reference') {
          // Make sure this Node Reference actually references a node.
          if ($summary_field['nid']) {
            $node = node_load($summary_field['nid']);
310
            $summary = $node->title;
311 312 313 314
          }
        }
        elseif ($summary_info['type'] == 'taxonomy_term_reference') {
          $terms = taxonomy_term_load_multiple($items);
315
          // Make sure that there are terms that were loaded.
316
          if ($terms) {
317
            $term_names = array();
318
            foreach ($terms as $term) {
319
              $term_names[] = $term->name;
320
            }
321
            $summary = implode(', ', $term_names);
322 323 324
          }
        }
        else {
325
          $summary = trim($summary_field['value']);
326
        }
327
        $data['summary'] = $summary ? $summary : $data['summary'];
328 329
      }
    }
330 331 332 333 334 335 336 337
    // Allow other modules to alter the HTML of the Summary and Description,
    // before it gets converted to iCal-compliant plaintext. This allows users
    // to set up a newline between fields, for instance.
    $context = array(
      'entity' => $entity,
      'entity_type' => $this->entity_type,
      'language' => $this->language,
    );
338
    drupal_alter('date_ical_export_html', $data, $this->view, $context);
Robert Rollins's avatar
Robert Rollins committed
339

Karen Stevenson's avatar
Karen Stevenson committed
340
    $event = array();
341 342
    $event['summary'] = date_ical_sanitize_text($data['summary']);
    $event['description'] = date_ical_sanitize_text($data['description']);
Karen Stevenson's avatar
Karen Stevenson committed
343
    $event['all_day'] = $all_day;
Robert Rollins's avatar
Robert Rollins committed
344 345
    $event['start'] = $start;
    $event['end'] = $end;
346 347 348
    $uri = entity_uri($this->entity_type, $entity);
    $uri['options']['absolute'] = TRUE;
    $event['url'] = url($uri['path'], $uri['options']);
349
    $event['rrule'] = $is_field && array_key_exists('rrule', $date_field) ? $date_field['rrule'] : '';
350
    if ($location) {
351
      $event['location'] = date_ical_sanitize_text($location);
352
    }
Robert Rollins's avatar
Robert Rollins committed
353

354 355
    // For this event's UID, use either the date_id generated by the Date
    // module, or the event page's URL if the date_id isn't available.
356
    $event['uid'] = !empty($entity->date_id) ? $entity->date_id[0] : $event['url'];
Robert Rollins's avatar
Robert Rollins committed
357

358 359 360
    // If we are using a repeat rule (and not just multi-day events) we
    // remove the item from the entities list so that its VEVENT won't be
    // re-created.
361 362 363
    if ($event['rrule']) {
      $this->entities[$id] = NULL;
    }
Robert Rollins's avatar
Robert Rollins committed
364

365 366
    // According to the iCal standard, CREATED and LAST-MODIFIED must be UTC.
    // Fortunately, Drupal stores timestamps in the DB as UTC, so we just need
367
    // to tell DateObject to treat the timestamp as UTC from the start.
368
    if (isset($entity->created)) {
369
      $event['created'] = new DateObject($entity->created, 'UTC');
370 371 372
    }
    // Pull the 'changed' date from the entity (if available), so that
    // subscription clients can tell if the event has been updated.
373
    if (isset($entity->changed)) {
374
      $event['last-modified'] = new DateObject($entity->changed, 'UTC');
375
    }
376
    elseif (isset($entity->created)) {
377
      // If changed is unset, but created is, use that for last-modified.
378
      $event['last-modified'] = new DateObject($entity->created, 'UTC');
379
    }
Robert Rollins's avatar
Robert Rollins committed
380

381
    // Allow other modules to alter the structured event object, before it gets
382 383
    // passed to the style plugin to be converted into an iCalcreator vevent.
    drupal_alter('date_ical_export_raw_event', $event, $this->view, $context);
Robert Rollins's avatar
Robert Rollins committed
384

Robert Rollins's avatar
Robert Rollins committed
385
    return $event;
Karen Stevenson's avatar
Karen Stevenson committed
386 387
  }
}