Projet

Général

Profil

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

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

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.6');
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 callback' => '_date_ical_detect_icalcreator_version',
169
    'versions' => array(
170
      '2.20' => array(
171
        'files' => array(
172
          'php' => array('iCalcreator.class.php'),
173
        ),
174
      ),
175
      '2.22+' => array(
176
        'files' => array(
177
          'php' => array('iCalcreator.php'),
178
        ),
179
      ),
180
    ),
181
  );
182

    
183
  return $libraries;
184
}
185

    
186
/**
187
 * Implements hook_help().
188
 */
189
function date_ical_help($path, $arg) {
190
  switch ($path) {
191
    case 'admin/help#date_ical':
192
      return '<pre>' . file_get_contents(drupal_get_path('module', 'date_ical') . "/README.txt") . '</pre>';
193
  }
194
}
195

    
196
/**
197
 * Implements hook_ctools_plugin_api().
198
 */
199
function date_ical_ctools_plugin_api($owner, $api) {
200
  if ($owner == 'feeds' && $api == 'plugins') {
201
    return array('version' => 2);
202
  }
203
}
204

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

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

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

    
247
  // Strip the remaining HTML.
248
  $text = strip_tags($text);
249
  // Remove newlines added at the beginning.
250
  return trim($text);
251
}
252

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

    
273
  if (is_array($repeat_rule)) {
274
    // Newer versions of Feeds return $repeat_rule as an array containing the
275
    // string we returned from ParseVcalendar::parseRepeatProperty().
276
    $repeat_rule = reset($repeat_rule);
277
  }
278

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

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

    
298
/**
299
 * Identify all potential fields which could be used as an iCal LOCATION.
300
 */
301
function date_ical_get_location_fields($base = 'node', $reset = FALSE) {
302
  static $fields = array();
303
  $empty = array('name' => array(), 'alias' => array());
304

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

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

    
335
  $cid = 'date_ical_location_fields_' . $base;
336
  cache_clear_all($cid, 'cache_views');
337

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

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

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

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

    
378
  cache_set($cid, $fields, 'cache_views');
379
  return $fields;
380
}
381

    
382

    
383
/**
384
 * Identify all potential fields which could be used as an iCal SUMMARY.
385
 */
386
function date_ical_get_summary_fields($base = 'node', $reset = FALSE) {
387
  static $fields = array();
388
  $empty = array('name' => array(), 'alias' => array());
389

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

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

    
420
  $cid = 'date_ical_summary_fields_' . $base;
421
  cache_clear_all($cid, 'cache_views');
422

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

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

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

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

    
462
  cache_set($cid, $fields, 'cache_views');
463
  return $fields;
464
}
465

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

    
501
function _date_ical_detect_icalcreator_version() {
502
  $path = libraries_get_path('iCalcreator');
503
  if (file_exists(DRUPAL_ROOT . "/$path/iCalcreator.class.php")) {
504
    return '2.20';
505
  }
506
  else if (file_exists(DRUPAL_ROOT . "/$path//iCalcreator.php")) {
507
    return '2.22+';
508
  }
509
  else {
510
    return FALSE;
511
  }
512
}