Projet

Général

Profil

Paste
Télécharger (15,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / date_ical / date_ical.module @ fc2c1c7a

1
<?php
2

    
3
/**
4
 * @file
5
 * Adds ical functionality to Views, and an iCal parser to Feeds.
6
 */
7

    
8
/**
9
 * The version number of the current release. This is inserted into the PRODID
10
 * value of the iCal feeds created by Date iCal, for debugging purposes.
11
 */
12
define('DATE_ICAL_VERSION', '3.8');
13

    
14
/**
15
 * Exception class for generic exceptions thrown by this module.
16
 */
17
class DateIcalException extends Exception {}
18

    
19
/**
20
 * Exception used by iCal Fields for when a date field is blank.
21
 */
22
class BlankDateFieldException extends DateIcalException {}
23

    
24
/**
25
 * Exception thrown when the Feeds parser plugin fails.
26
 */
27
class DateIcalParseException extends DateIcalException {}
28

    
29
/**
30
 * Implements hook_hook_info().
31
 */
32
function date_ical_hook_info() {
33
  $hooks = array(
34
    'date_ical_export_html_alter',
35
    'date_ical_export_raw_event_alter',
36
    'date_ical_export_vevent_alter',
37
    'date_ical_export_vcalendar_alter',
38
    'date_ical_export_post_render_alter',
39
    'date_ical_import_vcalendar_alter',
40
    'date_ical_import_component_alter',
41
    'date_ical_import_timezone_alter',
42
  );
43

    
44
  return array_fill_keys($hooks, array('group' => 'date_ical'));
45
}
46

    
47
/**
48
 * Implements hook_views_api().
49
 */
50
function date_ical_views_api() {
51
  return array(
52
    'api' => 3,
53
    'path' => drupal_get_path('module', 'date_ical') . '/includes',
54
  );
55
}
56

    
57
/**
58
 * Implements hook_feeds_plugins().
59
 */
60
function date_ical_feeds_plugins() {
61
  $path = drupal_get_path('module', 'date_ical') . '/includes';
62
  $info = array();
63
  $info['DateiCalFeedsParser'] = array(
64
    'name' => 'iCal parser',
65
    'description' => t('Parse iCal feeds.'),
66
    'handler' => array(
67
      'parent' => 'FeedsParser',
68
      'class' => 'DateiCalFeedsParser',
69
      'file' => 'DateiCalFeedsParser.inc',
70
      'path' => $path,
71
    ),
72
  );
73
  return $info;
74
}
75

    
76
/**
77
 * Implements hook_theme().
78
 */
79
function date_ical_theme($existing, $type, $theme, $path) {
80
  return array(
81
    'date_ical_icon' => array(
82
      'variables' => array('url' => NULL, 'tooltip' => NULL),
83
    ),
84
  );
85
}
86

    
87
/**
88
 * Theme function for the ical icon used by attached iCal feed.
89
 *
90
 * Available variables are:
91
 * $variables['tooltip'] - The tooltip to be used for the ican feed icon.
92
 * $variables['url'] - The url to the actual iCal feed.
93
 * $variables['view'] - The view object from which the iCal feed is being
94
 *   built (useful for contextual information).
95
 */
96
function theme_date_ical_icon($variables) {
97
  if (empty($variables['tooltip'])) {
98
    $variables['tooltip'] = t('Add this event to my calendar');
99
  }
100
  $variables['path'] = drupal_get_path('module', 'date_ical') . '/images/ical-feed-icon-34x14.png';
101
  $variables['alt'] = $variables['title'] = $variables['tooltip'];
102
  if ($image = theme('image', $variables)) {
103
    return "<a href='{$variables['url']}' class='ical-icon'>$image</a>";
104
  }
105
  else {
106
    return "<a href='{$variables['url']}' class='ical-icon'>{$variables['tooltip']}</a>";
107
  }
108
}
109

    
110
/**
111
 * Implements hook_preprocess_HOOK().
112
 *
113
 * Hides the page elements which don't belong in an iCal DESCRIPTION element.
114
 * The display of the body and other fields is controlled by the Manage
115
 * Display settings for the nodes' iCal view mode.
116
 */
117
function date_ical_preprocess_node(&$variables) {
118
  if (isset($variables['view_mode']) && $variables['view_mode'] == 'ical') {
119
    // Trick the default node template into not displaying the page title, by
120
    // telling it that this *is* a page.
121
    $variables['page'] = TRUE;
122
    $variables['title_prefix'] = '';
123
    $variables['title_suffix'] = '';
124

    
125
    // We don't want to see the author information in our feed.
126
    $variables['display_submitted'] = FALSE;
127

    
128
    // Comments and links don't belong in an iCal feed.
129
    if (isset($variables['content']['comments'])) {
130
      unset($variables['content']['comments']);
131
    }
132
    if (isset($variables['content']['links'])) {
133
      unset($variables['content']['links']);
134
    }
135
  }
136
}
137

    
138
/**
139
 * Implements hook_entity_info_alter().
140
 *
141
 * Adds an 'iCal' view mode for entities, which is used by the iCal Entity
142
 * View plugin.
143
 */
144
function date_ical_entity_info_alter(&$entity_info) {
145
  foreach ($entity_info as $entity_type => $info) {
146
    if (!isset($entity_info[$entity_type]['view modes'])) {
147
      $entity_info[$entity_type]['view modes'] = array();
148
    }
149
    $entity_info[$entity_type]['view modes'] += array(
150
      'ical' => array(
151
        'label' => t('iCal'),
152
        // Set the iCal view mode to default to same settings as the "default"
153
        // view mode, so it won't pollute Features.
154
        'custom settings' => FALSE,
155
      ),
156
    );
157
  }
158
}
159

    
160
/**
161
 * Implements hook_libraries_info().
162
 */
163
function date_ical_libraries_info() {
164
  $libraries['iCalcreator'] = array(
165
    'name' => 'iCalcreator',
166
    'vendor url' => 'http://github.com/iCalcreator/iCalcreator',
167
    'download url' => 'http://github.com/iCalcreator/iCalcreator',
168
    'version arguments' => array(
169
      'file' => 'iCalcreator.class.php',
170
      'pattern' => "/define.*?ICALCREATOR_VERSION.*?([\d\.]+)/",
171
      'lines' => 100,
172
    ),
173
    'files' => array(
174
      'php' => array('iCalcreator.class.php'),
175
    ),
176
  );
177

    
178
  return $libraries;
179
}
180

    
181
/**
182
 * Implements hook_help().
183
 */
184
function date_ical_help($path, $arg) {
185
  switch ($path) {
186
    case 'admin/help#date_ical':
187
      return '<pre>' . file_get_contents(drupal_get_path('module', 'date_ical') . "/README.txt") . '</pre>';
188
  }
189
}
190

    
191
/**
192
 * Implements hook_ctools_plugin_api().
193
 */
194
function date_ical_ctools_plugin_api($owner, $api) {
195
  if ($owner == 'feeds' && $api == 'plugins') {
196
    return array('version' => 2);
197
  }
198
}
199

    
200
/**
201
 * Implements hook_feeds_processor_targets_alter().
202
 *
203
 * Adds the "Field Name: Repeat Rule" target to Date Repeat fields.
204
 *
205
 * @see FeedsNodeProcessor::getMappingTargets()
206
 */
207
function date_ical_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
208
  foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
209
    $info = field_info_field($name);
210
    if (in_array($info['type'], array('date', 'datestamp', 'datetime')) && isset($info['settings']['repeat']) && $info['settings']['repeat']) {
211
      $targets[$name . ':rrule'] = array(
212
        'name' => t('@name: Repeat Rule', array('@name' => $instance['label'])),
213
        'callback' => 'date_ical_feeds_set_rrule',
214
        'description' => t('The repeat rule for the @name field.', array('@name' => $instance['label'])),
215
        'real_target' => $name,
216
      );
217
    }
218
  }
219
}
220

    
221
/**
222
 * Reformats the provided text to be compliant with the iCal spec.
223
 *
224
 * If the text contains HTML tags, those tags will be stripped. Paragraph,
225
 * heading, and div tags will be replaced with newlines.
226
 *
227
 * @param string $text
228
 *   The text to be sanitized.
229
 */
230
function date_ical_sanitize_text($text = '') {
231
  // HTML tags may get converted to &lt; and such by the View code, so we need
232
  // to convert them back to HTML so we can remove them with strip_tags().
233
  $text = decode_entities($text);
234

    
235
  // Convert <p> tags to double newlines.
236
  $text = trim(preg_replace("/<p.*?>/i", "\n\n", $text));
237
  // Separate heading tags from the text around them in both directions.
238
  $text = trim(preg_replace("/<\\?h\d.*?>/i", "\n\n", $text));
239
  // Add a newline for each <div>.
240
  $text = trim(preg_replace("/<div.*?>/i", "\n", $text));
241

    
242
  // Strip the remaining HTML.
243
  $text = strip_tags($text);
244
  // Remove newlines added at the beginning.
245
  return trim($text);
246
}
247

    
248
/**
249
 * Callback specified in date_ical_feeds_processor_targets_alter() for RRULEs.
250
 *
251
 * @param object $source
252
 *   The FeedsSource object.
253
 * @param object $entity
254
 *   The node that's being built from the iCal element that's being parsed.
255
 * @param string $target
256
 *   The machine name of the field into which this RRULE shall be parsed,
257
 *   with ":rrule" appended to the end.
258
 * @param string|array $repeat_rule
259
 *   The repeat rule string, formatted like "$rrule|$rdate|$exrule|$exdate".
260
 *   Newer versions of Feeds send this value as an array containing the string.
261
 */
262
function date_ical_feeds_set_rrule($source, $entity, $target, $repeat_rule) {
263
  if (empty($repeat_rule)) {
264
    // Don't alter the entity if there's no repeat rule.
265
    return;
266
  }
267

    
268
  if (is_array($repeat_rule)) {
269
    // Newer versions of Feeds return $repeat_rule as an array containing the
270
    // string we returned from ParseVcalendar::parseRepeatProperty().
271
    $repeat_rule = reset($repeat_rule);
272
  }
273

    
274
  // $target looks like <field_name>:rrule, but we only need <field_name>.
275
  $field_name = current(explode(':', $target, 2));
276
  // Parse the repeat rule into RRULE, RDATE, EXRULE, and EXDATE strings.
277
  $repeat_data = array_combine(
278
    array('RRULE', 'RDATE', 'EXRULE', 'EXDATE'),
279
    explode('|', $repeat_rule)
280
  );
281

    
282
  module_load_include('inc', 'date_ical', 'date_ical.utils');
283
  // This "loop" is really just to make sure we get the right array keys. It
284
  // souldn't ever execute more than once.
285
  foreach ($entity->{$field_name} as $lang => $date_values) {
286
    $values = _date_ical_get_repeat_dates($field_name, $repeat_data, $date_values[0], $source);
287
    foreach ($values as $key => $value) {
288
      $entity->{$field_name}[$lang][$key] = $value;
289
    }
290
  }
291
}
292

    
293
/**
294
 * Identify all potential fields which could be used as an iCal LOCATION.
295
 */
296
function date_ical_get_location_fields($base = 'node', $reset = FALSE) {
297
  static $fields = array();
298
  $empty = array('name' => array(), 'alias' => array());
299

    
300
  if (empty($fields[$base]) || $reset) {
301
    $cid = 'date_ical_location_fields_' . $base;
302
    if (!$reset && $cached = cache_get($cid, 'cache_views')) {
303
      $fields[$base] = $cached->data;
304
    }
305
    else {
306
      $fields[$base] = _date_ical_get_location_fields($base);
307
    }
308
  }
309
  // Make sure that empty values will be arrays in the expected format.
310
  return !empty($fields) && !empty($fields[$base]) ? $fields[$base] : $empty;
311
}
312

    
313
/**
314
 * Internal helper function for date_ical_get_location_fields().
315
 *
316
 * This is a cut down version of _date_views_fields() from
317
 * date_views_fields.inc in date_views module.
318
 *
319
 * @return array
320
 *   array with fieldname, type, and table.
321
 *
322
 * @see date_views_date_views_fields()
323
 */
324
function _date_ical_get_location_fields($base = 'node') {
325
  // Make sure $base is never empty.
326
  if (empty($base)) {
327
    $base = 'node';
328
  }
329

    
330
  $cid = 'date_ical_location_fields_' . $base;
331
  cache_clear_all($cid, 'cache_views');
332

    
333
  // Iterate over all the fields that Views knows about.
334
  $all_fields = date_views_views_fetch_fields($base, 'field');
335
  $fields = array();
336
  foreach ($all_fields as $alias => $val) {
337
    $name = $alias;
338
    $tmp = explode('.', $name);
339
    $field_name = $tmp[1];
340
    $table_name = $tmp[0];
341

    
342
    // Skip unsupported field types and fields that weren't defined through
343
    // the Field module.
344
    $info = field_info_field($field_name);
345
    $supported_location_fields = array(
346
      'text',
347
      'text_long',
348
      'text_with_summary',
349
      'node_reference',
350
      'addressfield',
351
      'location'
352
    );
353
    if (!$info || !in_array($info['type'], $supported_location_fields)) {
354
      continue;
355
    }
356

    
357
    // Build an array of the field info that we'll need.
358
    $alias = str_replace('.', '_', $alias);
359
    $fields['name'][$name] = array(
360
      'label' => "{$val['group']}: {$val['title']} ($field_name)",
361
      'table_name' => $table_name,
362
      'field_name' => $field_name,
363
      'type' => $info['type'],
364
    );
365

    
366
    // These are here only to make this $field array conform to the same format
367
    // as the one returned by _date_views_fields(). They're probably not needed,
368
    // but I thought that consistency would be a good idea.
369
    $fields['name'][$name]['real_field_name'] = $field_name;
370
    $fields['alias'][$alias] = $fields['name'][$name];
371
  }
372

    
373
  cache_set($cid, $fields, 'cache_views');
374
  return $fields;
375
}
376

    
377

    
378
/**
379
 * Identify all potential fields which could be used as an iCal SUMMARY.
380
 */
381
function date_ical_get_summary_fields($base = 'node', $reset = FALSE) {
382
  static $fields = array();
383
  $empty = array('name' => array(), 'alias' => array());
384

    
385
  if (empty($fields[$base]) || $reset) {
386
    $cid = 'date_ical_summary_fields_' . $base;
387
    if (!$reset && $cached = cache_get($cid, 'cache_views')) {
388
      $fields[$base] = $cached->data;
389
    }
390
    else {
391
      $fields[$base] = _date_ical_get_summary_fields($base);
392
    }
393
  }
394
  // Make sure that empty values will be arrays in the expected format.
395
  return !empty($fields) && !empty($fields[$base]) ? $fields[$base] : $empty;
396
}
397

    
398
/**
399
 * Internal helper function for date_ical_get_summary_fields().
400
 *
401
 * This is a cut down version of _date_views_fields() from
402
 * date_views_fields.inc in date_views module.
403
 *
404
 * @return array
405
 *   Array with fieldname, type, and table.
406
 *
407
 * @see date_views_date_views_fields()
408
 */
409
function _date_ical_get_summary_fields($base = 'node') {
410
  // Make sure $base is never empty.
411
  if (empty($base)) {
412
    $base = 'node';
413
  }
414

    
415
  $cid = 'date_ical_summary_fields_' . $base;
416
  cache_clear_all($cid, 'cache_views');
417

    
418
  // Iterate over all the fields that Views knows about.
419
  $all_fields = date_views_views_fetch_fields($base, 'field');
420
  $fields = array();
421
  foreach ($all_fields as $alias => $val) {
422
    $name = $alias;
423
    $tmp = explode('.', $name);
424
    $field_name = $tmp[1];
425
    $table_name = $tmp[0];
426

    
427
    // Skip unsupported field types and fields that weren't defined through
428
    // the Field module.
429
    $info = field_info_field($field_name);
430
    $supported_summary_fields = array(
431
      'text',
432
      'text_long',
433
      'text_with_summary',
434
      'node_reference',
435
      'taxonomy_term_reference',
436
    );
437
    if (!$info || !in_array($info['type'], $supported_summary_fields)) {
438
      continue;
439
    }
440

    
441
    // Build an array of the field info that we'll need.
442
    $alias = str_replace('.', '_', $alias);
443
    $fields['name'][$name] = array(
444
      'label' => "{$val['group']}: {$val['title']} ($field_name)",
445
      'table_name' => $table_name,
446
      'field_name' => $field_name,
447
      'type' => $info['type'],
448
    );
449

    
450
    // These are here only to make this $field array conform to the same format
451
    // as the one returned by _date_views_fields(). They're probably not needed,
452
    // but I thought that consistency would be a good idea.
453
    $fields['name'][$name]['real_field_name'] = $field_name;
454
    $fields['alias'][$alias] = $fields['name'][$name];
455
  }
456

    
457
  cache_set($cid, $fields, 'cache_views');
458
  return $fields;
459
}
460

    
461
/**
462
 * Convert an rrule array to the iCalcreator format.
463
 *
464
 * iCalcreator expects the BYDAY element to be an array like this:
465
 * (array) ( [([plus] ordwk / minus ordwk)], "DAY" => weekday )
466
 *
467
 * But the way that the Date API gives it to us is like this:
468
 * (array) ( [([plus] ordwk / minus ordwk)]weekday )
469
 */
470
function _date_ical_convert_rrule_for_icalcreator($rrule) {
471
  $new_rrule = array();
472
  foreach ($rrule as $key => $value) {
473
    if (strtoupper($key) == 'DATA') {
474
      // iCalcreator doesn't expect the 'DATA' key that the Date API gives us.
475
      continue;
476
    }
477
    if (strtoupper($key) == 'UNTIL') {
478
      // iCalcreator expects the 'timestamp' to be array key for UNTIL.
479
      $value['timestamp'] = strtotime($value['datetime']);
480
    }
481
    if (strtoupper($key) == 'BYDAY') {
482
      $new_byday = array();
483
      foreach ($value as $day) {
484
        // Fortunately, the weekday values are always 2 characters.
485
        $weekday = substr($day, -2);
486
        $ordwk = substr($day, 0, -2);
487
        $new_byday[] = array($ordwk, 'DAY' => $weekday);
488
      }
489
      $value = $new_byday;
490
    }
491
    $new_rrule[$key] = $value;
492
  }
493
  return $new_rrule;
494
}