1 |
85ad3d82
|
Assos Assos
|
<?php
|
2 |
|
|
|
3 |
|
|
/**
|
4 |
|
|
* @file
|
5 |
|
|
* Contains the iCal row style plugin.
|
6 |
|
|
*/
|
7 |
|
|
|
8 |
|
|
/**
|
9 |
8a7e43dd
|
Florent Torregrosa
|
* A Views plugin which builds an iCal VEVENT from a single node.
|
10 |
85ad3d82
|
Assos Assos
|
*/
|
11 |
|
|
class date_ical_plugin_row_ical_entity extends views_plugin_row {
|
12 |
|
|
|
13 |
|
|
// Basic properties that let the row style follow relationships.
|
14 |
8a7e43dd
|
Florent Torregrosa
|
protected $base_table = 'node';
|
15 |
|
|
protected $base_field = 'nid';
|
16 |
85ad3d82
|
Assos Assos
|
|
17 |
|
|
// Stores the nodes loaded with pre_render.
|
18 |
8a7e43dd
|
Florent Torregrosa
|
protected $entities = array();
|
19 |
85ad3d82
|
Assos Assos
|
|
20 |
8a7e43dd
|
Florent Torregrosa
|
/**
|
21 |
|
|
* Initialize the row plugin.
|
22 |
|
|
*/
|
23 |
|
|
public function init(&$view, &$display, $options = NULL) {
|
24 |
85ad3d82
|
Assos Assos
|
parent::init($view, $display, $options);
|
25 |
|
|
$this->base_table = $view->base_table;
|
26 |
|
|
$this->base_field = $view->base_field;
|
27 |
|
|
}
|
28 |
|
|
|
29 |
8a7e43dd
|
Florent Torregrosa
|
/**
|
30 |
|
|
* Set up the options for the row plugin.
|
31 |
|
|
*/
|
32 |
|
|
public function option_definition() {
|
33 |
85ad3d82
|
Assos Assos
|
$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 |
8a7e43dd
|
Florent Torregrosa
|
* Build the form for setting the row plugin's options.
|
42 |
85ad3d82
|
Assos Assos
|
*/
|
43 |
8a7e43dd
|
Florent Torregrosa
|
public function options_form(&$form, &$form_state) {
|
44 |
85ad3d82
|
Assos Assos
|
parent::options_form($form, $form_state);
|
45 |
|
|
|
46 |
8a7e43dd
|
Florent Torregrosa
|
// Build the select dropdown for the Date field that the user wants to use
|
47 |
|
|
// to populate the date properties in VEVENTs.
|
48 |
85ad3d82
|
Assos Assos
|
$data = date_views_fields($this->base_table);
|
49 |
|
|
$options = array();
|
50 |
|
|
foreach ($data['name'] as $item => $value) {
|
51 |
8a7e43dd
|
Florent Torregrosa
|
// We only want to see one value for each field, so we need to
|
52 |
|
|
// skip '_value2' and other columns.
|
53 |
85ad3d82
|
Assos Assos
|
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 |
8a7e43dd
|
Florent Torregrosa
|
// The surrounding <div> ensures that the settings dialog expands.
|
68 |
85ad3d82
|
Assos Assos
|
'#prefix' => '<div style="font-size: 90%">',
|
69 |
|
|
'#suffix' => '</div>',
|
70 |
c7b88c87
|
Assos Assos
|
'#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 |
85ad3d82
|
Assos Assos
|
<br>To change the iCal view mode, configure it on the 'Manage Display' page for each Content Type.
|
72 |
c7b88c87
|
Assos Assos
|
Please note that all HTML will be stripped from the output, to comply with iCal standards."),
|
73 |
85ad3d82
|
Assos Assos
|
);
|
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 |
8a7e43dd
|
Florent Torregrosa
|
/**
|
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 |
85ad3d82
|
Assos Assos
|
// 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 |
8a7e43dd
|
Florent Torregrosa
|
// Use the $id as the key so we create only value per entity.
|
125 |
85ad3d82
|
Assos Assos
|
$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 |
8a7e43dd
|
Florent Torregrosa
|
/**
|
153 |
|
|
* Renders the entities returned by the view into event arrays.
|
154 |
|
|
*/
|
155 |
|
|
public function render($row) {
|
156 |
85ad3d82
|
Assos Assos
|
$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 |
8a7e43dd
|
Florent Torregrosa
|
// Sometimes the timestamp is actually the revision timestamp.
|
175 |
85ad3d82
|
Assos Assos
|
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 |
8a7e43dd
|
Florent Torregrosa
|
// our view to use. We can't do anything with it.
|
182 |
85ad3d82
|
Assos Assos
|
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 |
8a7e43dd
|
Florent Torregrosa
|
global $base_url;
|
200 |
|
|
$domain = preg_replace('#^https?://#', '', $base_url);
|
201 |
85ad3d82
|
Assos Assos
|
$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 |
8a7e43dd
|
Florent Torregrosa
|
if (!empty($date_field['value2'])) {
|
206 |
85ad3d82
|
Assos Assos
|
$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 |
8a7e43dd
|
Florent Torregrosa
|
$start->setTimezone(new DateTimeZone($date_field['timezone']));
|
220 |
|
|
$end->setTimezone(new DateTimeZone($date_field['timezone']));
|
221 |
85ad3d82
|
Assos Assos
|
|
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 |
ca0757b9
|
Assos Assos
|
$location = $node->title;
|
252 |
85ad3d82
|
Assos Assos
|
}
|
253 |
|
|
}
|
254 |
|
|
elseif ($location_info['type'] == 'addressfield') {
|
255 |
|
|
$locations = array();
|
256 |
8a7e43dd
|
Florent Torregrosa
|
foreach ($location_field as $key => $loc) {
|
257 |
85ad3d82
|
Assos Assos
|
if ($loc && !in_array($key, array('first_name', 'last_name'))) {
|
258 |
|
|
$locations[] = $loc;
|
259 |
|
|
}
|
260 |
|
|
}
|
261 |
|
|
$location = implode(', ', array_reverse($locations));
|
262 |
|
|
}
|
263 |
8a7e43dd
|
Florent Torregrosa
|
elseif ($location_info['type'] == 'location') {
|
264 |
|
|
$included_fields = array(
|
265 |
|
|
'name',
|
266 |
|
|
'additional',
|
267 |
|
|
'street',
|
268 |
|
|
'city',
|
269 |
|
|
'province_name',
|
270 |
|
|
'postal_code',
|
271 |
|
|
'country_name'
|
272 |
|
|
);
|
273 |
|
|
$location_data = array();
|
274 |
|
|
foreach ($included_fields as $included_field) {
|
275 |
|
|
if (!empty($location_field[$included_field])) {
|
276 |
|
|
$location_data[] = $location_field[$included_field];
|
277 |
|
|
}
|
278 |
|
|
}
|
279 |
ca0757b9
|
Assos Assos
|
$location = implode(', ', $location_data);
|
280 |
8a7e43dd
|
Florent Torregrosa
|
}
|
281 |
85ad3d82
|
Assos Assos
|
else {
|
282 |
ca0757b9
|
Assos Assos
|
$location = $location_field['value'];
|
283 |
85ad3d82
|
Assos Assos
|
}
|
284 |
|
|
}
|
285 |
|
|
}
|
286 |
|
|
|
287 |
8a7e43dd
|
Florent Torregrosa
|
// Create the rendered event using the display settings from the
|
288 |
|
|
// iCal view mode.
|
289 |
85ad3d82
|
Assos Assos
|
$rendered_array = entity_view($this->entity_type, array($entity), 'ical', $this->language, TRUE);
|
290 |
|
|
$data = array(
|
291 |
|
|
'description' => drupal_render($rendered_array),
|
292 |
8a7e43dd
|
Florent Torregrosa
|
'summary' => entity_label($this->entity_type, $entity),
|
293 |
85ad3d82
|
Assos Assos
|
);
|
294 |
|
|
if (!empty($this->options['summary_field']) && $this->options['summary_field'] != 'default_title') {
|
295 |
|
|
$summary_fields = date_ical_get_summary_fields();
|
296 |
|
|
$summary_info = $summary_fields['name'][$this->options['summary_field']];
|
297 |
|
|
$summary_field_name = $summary_info['real_field_name'];
|
298 |
|
|
// Only attempt this is the entity actually has this field.
|
299 |
|
|
$items = field_get_items($this->entity_type, $entity, $summary_field_name);
|
300 |
|
|
$summary = '';
|
301 |
|
|
if ($items) {
|
302 |
|
|
$summary_field = $items[0];
|
303 |
|
|
if ($summary_info['type'] == 'node_reference') {
|
304 |
|
|
// Make sure this Node Reference actually references a node.
|
305 |
|
|
if ($summary_field['nid']) {
|
306 |
|
|
$node = node_load($summary_field['nid']);
|
307 |
|
|
$summary = $node->title;
|
308 |
|
|
}
|
309 |
|
|
}
|
310 |
|
|
elseif ($summary_info['type'] == 'taxonomy_term_reference') {
|
311 |
|
|
$terms = taxonomy_term_load_multiple($items);
|
312 |
8a7e43dd
|
Florent Torregrosa
|
// Make sure that there are terms that were loaded.
|
313 |
85ad3d82
|
Assos Assos
|
if ($terms) {
|
314 |
|
|
$term_names = array();
|
315 |
|
|
foreach ($terms as $term) {
|
316 |
|
|
$term_names[] = $term->name;
|
317 |
|
|
}
|
318 |
|
|
$summary = implode(', ', $term_names);
|
319 |
|
|
}
|
320 |
|
|
}
|
321 |
|
|
else {
|
322 |
|
|
$summary = trim($summary_field['value']);
|
323 |
|
|
}
|
324 |
8a7e43dd
|
Florent Torregrosa
|
$data['summary'] = $summary ? $summary : $data['summary'];
|
325 |
85ad3d82
|
Assos Assos
|
}
|
326 |
|
|
}
|
327 |
|
|
// Allow other modules to alter the HTML of the Summary and Description,
|
328 |
|
|
// before it gets converted to iCal-compliant plaintext. This allows users
|
329 |
|
|
// to set up a newline between fields, for instance.
|
330 |
|
|
$context = array(
|
331 |
|
|
'entity' => $entity,
|
332 |
|
|
'entity_type' => $this->entity_type,
|
333 |
|
|
'language' => $this->language,
|
334 |
|
|
);
|
335 |
8a7e43dd
|
Florent Torregrosa
|
drupal_alter('date_ical_export_html', $data, $this->view, $context);
|
336 |
85ad3d82
|
Assos Assos
|
|
337 |
|
|
$event = array();
|
338 |
|
|
$event['summary'] = date_ical_sanitize_text($data['summary']);
|
339 |
|
|
$event['description'] = date_ical_sanitize_text($data['description']);
|
340 |
|
|
$event['all_day'] = $all_day;
|
341 |
|
|
$event['start'] = $start;
|
342 |
|
|
$event['end'] = $end;
|
343 |
|
|
$uri = entity_uri($this->entity_type, $entity);
|
344 |
|
|
$uri['options']['absolute'] = TRUE;
|
345 |
|
|
$event['url'] = url($uri['path'], $uri['options']);
|
346 |
|
|
$event['rrule'] = $is_field && array_key_exists('rrule', $date_field) ? $date_field['rrule'] : '';
|
347 |
|
|
if ($location) {
|
348 |
ca0757b9
|
Assos Assos
|
$event['location'] = date_ical_sanitize_text($location);
|
349 |
85ad3d82
|
Assos Assos
|
}
|
350 |
|
|
|
351 |
8a7e43dd
|
Florent Torregrosa
|
// For this event's UID, use either the date_id generated by the Date
|
352 |
|
|
// module, or the event page's URL if the date_id isn't available.
|
353 |
85ad3d82
|
Assos Assos
|
$event['uid'] = !empty($entity->date_id) ? $entity->date_id[0] : $event['url'];
|
354 |
|
|
|
355 |
|
|
// If we are using a repeat rule (and not just multi-day events) we
|
356 |
|
|
// remove the item from the entities list so that its VEVENT won't be
|
357 |
|
|
// re-created.
|
358 |
|
|
if ($event['rrule']) {
|
359 |
|
|
$this->entities[$id] = NULL;
|
360 |
|
|
}
|
361 |
|
|
|
362 |
|
|
// According to the iCal standard, CREATED and LAST-MODIFIED must be UTC.
|
363 |
|
|
// Fortunately, Drupal stores timestamps in the DB as UTC, so we just need
|
364 |
8a7e43dd
|
Florent Torregrosa
|
// to tell DateObject to treat the timestamp as UTC from the start.
|
365 |
85ad3d82
|
Assos Assos
|
if (isset($entity->created)) {
|
366 |
8a7e43dd
|
Florent Torregrosa
|
$event['created'] = new DateObject($entity->created, 'UTC');
|
367 |
85ad3d82
|
Assos Assos
|
}
|
368 |
|
|
// Pull the 'changed' date from the entity (if available), so that
|
369 |
|
|
// subscription clients can tell if the event has been updated.
|
370 |
|
|
if (isset($entity->changed)) {
|
371 |
8a7e43dd
|
Florent Torregrosa
|
$event['last-modified'] = new DateObject($entity->changed, 'UTC');
|
372 |
85ad3d82
|
Assos Assos
|
}
|
373 |
8a7e43dd
|
Florent Torregrosa
|
elseif (isset($entity->created)) {
|
374 |
85ad3d82
|
Assos Assos
|
// If changed is unset, but created is, use that for last-modified.
|
375 |
8a7e43dd
|
Florent Torregrosa
|
$event['last-modified'] = new DateObject($entity->created, 'UTC');
|
376 |
85ad3d82
|
Assos Assos
|
}
|
377 |
|
|
|
378 |
|
|
// Allow other modules to alter the structured event object, before it gets
|
379 |
8a7e43dd
|
Florent Torregrosa
|
// passed to the style plugin to be converted into an iCalcreator vevent.
|
380 |
|
|
drupal_alter('date_ical_export_raw_event', $event, $this->view, $context);
|
381 |
85ad3d82
|
Assos Assos
|
|
382 |
|
|
return $event;
|
383 |
|
|
}
|
384 |
|
|
} |