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
|
else {
|
288
|
$location = $location_field['value'];
|
289
|
}
|
290
|
}
|
291
|
}
|
292
|
|
293
|
// Create the rendered event using the display settings from the
|
294
|
// iCal view mode.
|
295
|
$rendered_array = entity_view($this->entity_type, array($entity), 'ical', $this->language, TRUE);
|
296
|
$data = array(
|
297
|
'description' => drupal_render($rendered_array),
|
298
|
'summary' => entity_label($this->entity_type, $entity),
|
299
|
);
|
300
|
if (!empty($this->options['summary_field']) && $this->options['summary_field'] != 'default_title') {
|
301
|
$summary_fields = date_ical_get_summary_fields();
|
302
|
$summary_info = $summary_fields['name'][$this->options['summary_field']];
|
303
|
$summary_field_name = $summary_info['real_field_name'];
|
304
|
// Only attempt this is the entity actually has this field.
|
305
|
$items = field_get_items($this->entity_type, $entity, $summary_field_name);
|
306
|
$summary = '';
|
307
|
if ($items) {
|
308
|
$summary_field = $items[0];
|
309
|
if ($summary_info['type'] == 'node_reference') {
|
310
|
// Make sure this Node Reference actually references a node.
|
311
|
if ($summary_field['nid']) {
|
312
|
$node = node_load($summary_field['nid']);
|
313
|
$summary = $node->title;
|
314
|
}
|
315
|
}
|
316
|
elseif ($summary_info['type'] == 'taxonomy_term_reference') {
|
317
|
$terms = taxonomy_term_load_multiple($items);
|
318
|
// Make sure that there are terms that were loaded.
|
319
|
if ($terms) {
|
320
|
$term_names = array();
|
321
|
foreach ($terms as $term) {
|
322
|
$term_names[] = $term->name;
|
323
|
}
|
324
|
$summary = implode(', ', $term_names);
|
325
|
}
|
326
|
}
|
327
|
else {
|
328
|
$summary = trim($summary_field['value']);
|
329
|
}
|
330
|
$data['summary'] = $summary ? $summary : $data['summary'];
|
331
|
}
|
332
|
}
|
333
|
// Allow other modules to alter the HTML of the Summary and Description,
|
334
|
// before it gets converted to iCal-compliant plaintext. This allows users
|
335
|
// to set up a newline between fields, for instance.
|
336
|
$context = array(
|
337
|
'entity' => $entity,
|
338
|
'entity_type' => $this->entity_type,
|
339
|
'language' => $this->language,
|
340
|
);
|
341
|
drupal_alter('date_ical_export_html', $data, $this->view, $context);
|
342
|
|
343
|
$event = array();
|
344
|
$event['summary'] = date_ical_sanitize_text($data['summary']);
|
345
|
$event['description'] = date_ical_sanitize_text($data['description']);
|
346
|
$event['all_day'] = $all_day;
|
347
|
$event['start'] = $start;
|
348
|
$event['end'] = $end;
|
349
|
$uri = entity_uri($this->entity_type, $entity);
|
350
|
$uri['options']['absolute'] = TRUE;
|
351
|
$event['url'] = url($uri['path'], $uri['options']);
|
352
|
if ($is_field && !empty($date_field['rrule'])) {
|
353
|
$event['rrule'] = $date_field['rrule'];
|
354
|
}
|
355
|
if ($location) {
|
356
|
$event['location'] = date_ical_sanitize_text($location);
|
357
|
}
|
358
|
|
359
|
// For this event's UID, use either the date_id generated by the Date
|
360
|
// module, or the event page's URL if the date_id isn't available.
|
361
|
$event['uid'] = !empty($entity->date_id) ? $entity->date_id[0] : $event['url'];
|
362
|
|
363
|
// If we are using a repeat rule (and not just multi-day events) we
|
364
|
// remove the item from the entities list so that its VEVENT won't be
|
365
|
// re-created.
|
366
|
if (!empty($event['rrule'])) {
|
367
|
$this->entities[$id] = NULL;
|
368
|
}
|
369
|
|
370
|
// According to the iCal standard, CREATED and LAST-MODIFIED must be UTC.
|
371
|
// Fortunately, Drupal stores timestamps in the DB as UTC, so we just need
|
372
|
// to tell DateObject to treat the timestamp as UTC from the start.
|
373
|
if (isset($entity->created)) {
|
374
|
$event['created'] = new DateObject($entity->created, 'UTC');
|
375
|
}
|
376
|
// Pull the 'changed' date from the entity (if available), so that
|
377
|
// subscription clients can tell if the event has been updated.
|
378
|
if (isset($entity->changed)) {
|
379
|
$event['last-modified'] = new DateObject($entity->changed, 'UTC');
|
380
|
}
|
381
|
elseif (isset($entity->created)) {
|
382
|
// If changed is unset, but created is, use that for last-modified.
|
383
|
$event['last-modified'] = new DateObject($entity->created, 'UTC');
|
384
|
}
|
385
|
|
386
|
// Allow other modules to alter the structured event object, before it gets
|
387
|
// passed to the style plugin to be converted into an iCalcreator vevent.
|
388
|
drupal_alter('date_ical_export_raw_event', $event, $this->view, $context);
|
389
|
|
390
|
return $event;
|
391
|
}
|
392
|
}
|