Projet

Général

Profil

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

root / drupal7 / sites / all / modules / date_ical / date_ical.module @ 55670b15

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. It's primarily used for
11
 * debugging.
12
 */
13
define('DATE_ICAL_VERSION', '3.2');
14

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

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

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

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

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

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

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

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

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

    
111
/**
112
 * Implements hook_preprocess_HOOK().
113
 *
114
 * Hides the page elements which don't belong in an iCal DESCRIPTION element.
115
 * The display of the body and other fields is controlled by the Manage
116
 * Display settings for the nodes' iCal view mode.
117
 */
118
function date_ical_preprocess_node(&$variables) {
119
  if (isset($variables['view_mode']) && $variables['view_mode'] == 'ical') {
120
    // Trick the default node template into not displaying the page title, by
121
    // telling it that this *is* a page.
122
    $variables['page'] = TRUE;
123
    $variables['title_prefix'] = '';
124
    $variables['title_suffix'] = '';
125
    
126
    // We don't want to see the author information in our feed.
127
    $variables['display_submitted'] = FALSE;
128
    
129
    // Comments and links don't belong in an iCal feed.
130
    if (isset($variables['content']['comments'])) {
131
      unset($variables['content']['comments']);
132
    }
133
    if (isset($variables['content']['links'])) {
134
      unset($variables['content']['links']);
135
    }
136
  }
137
}
138

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

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

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

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

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

    
222
/**
223
 * Reformats the provided text to be compliant with the iCal spec.
224
 *
225
 * If the text contains HTML tags, those tags will be stripped (with <p> tags
226
 * converted to "\n\n" and link tags converted to footnotes), and uneeded
227
 * whitespace will be cleaned up.
228
 *
229
 * @param string $text
230
 *   The text to be sanitized.
231
 */
232
function date_ical_sanitize_text($text = '') {
233
  // Use Drupal's built-in HTML to Text converter, which does a mostly
234
  // adequate job of making the text iCal-compliant.
235
  $text = trim(drupal_html_to_text($text));
236
  // Replace instances of more than one space with exactly one space. This
237
  // cleans up the whitespace mess that drupal_html_to_text() leaves behind.
238
  $text = preg_replace("/  +/", " ", $text);
239
  // The call to drupal_html_to_text() above converted <p> to \n\n, and also
240
  // shoved a \n into the string every 80 characters. We don't want those
241
  // single \n's lying around, because iCalcreator will properly "fold" long
242
  // text fields for us. So, we need to remove all instances of \n which
243
  // are neither immediately preceeded, nor followed, by another \n.
244
  // However, \n's which are followed immediately by a > character should
245
  // remain, because of how drupal_html_to_text() converts <blockquote>.
246
  $text = preg_replace("/(?<!\n)\n(?![\n\>])/", " ", $text);
247
  return $text;
248
}
249

    
250
/**
251
 * Callback specified in date_ical_feeds_processor_targets_alter() for RRULEs.
252
 *
253
 * @param object $source
254
 *   The FeedsSource object.
255
 * @param object $entity
256
 *   The node that's being built from the iCal element that's being parsed.
257
 * @param string $target
258
 *   The machine name of the field into which this RRULE shall be parsed,
259
 *   with ":rrule" appended to the end.
260
 * @param string $repeat_rule
261
 *   The repeat rule string, formatted like "$rrule|$rdate|$exrule|$exdate".
262
 */
263
function date_ical_feeds_set_rrule($source, $entity, $target, $repeat_rule) {
264
  if (empty($repeat_rule)) {
265
    // Don't alter the entity if there's no repeat rule.
266
    return;
267
  }
268
  // $target looks like <field_name>:rrule, but we only need <field_name>.
269
  $field_name = current(explode(':', $target, 2));
270
  // Parse the repeat rule into RRULE, RDATE, EXRULE, and EXDATE strings.
271
  $repeat_data = array_combine(
272
    array('RRULE', 'RDATE', 'EXRULE', 'EXDATE'),
273
    explode('|', $repeat_rule)
274
  );
275
  
276
  module_load_include('inc', 'date_ical', 'date_ical.utils');
277
  // This "loop" is really just to make sure we get the right array keys. It
278
  // souldn't ever execute more than once.
279
  foreach ($entity->{$field_name} as $lang => $date_values) {
280
    $values = _date_ical_get_repeat_dates($field_name, $repeat_data, $date_values[0], $source);
281
    foreach ($values as $key => $value) {
282
      $entity->{$field_name}[$lang][$key] = $value;
283
    }
284
  }
285
}
286

    
287
/**
288
 * Identify all potential fields which could be used as an iCal LOCATION.
289
 */
290
function date_ical_get_location_fields($base = 'node', $reset = FALSE) {
291
  static $fields = array();
292
  $empty = array('name' => array(), 'alias' => array());
293
  
294
  if (empty($fields[$base]) || $reset) {
295
    $cid = 'date_ical_location_fields_' . $base;
296
    if (!$reset && $cached = cache_get($cid, 'cache_views')) {
297
      $fields[$base] = $cached->data;
298
    }
299
    else {
300
      $fields[$base] = _date_ical_get_location_fields($base);
301
    }
302
  }
303
  // Make sure that empty values will be arrays in the expected format.
304
  return !empty($fields) && !empty($fields[$base]) ? $fields[$base] : $empty;
305
}
306

    
307
/**
308
 * Internal helper function for date_ical_get_location_fields().
309
 *
310
 * This is a cut down version of _date_views_fields() from
311
 * date_views_fields.inc in date_views module.
312
 *
313
 * @return array
314
 *   array with fieldname, type, and table.
315
 *
316
 * @see date_views_date_views_fields()
317
 */
318
function _date_ical_get_location_fields($base = 'node') {
319
  // Make sure $base is never empty.
320
  if (empty($base)) {
321
    $base = 'node';
322
  }
323
  
324
  $cid = 'date_ical_location_fields_' . $base;
325
  cache_clear_all($cid, 'cache_views');
326
  
327
  // Iterate over all the fields that Views knows about.
328
  $all_fields = date_views_views_fetch_fields($base, 'field');
329
  $fields = array();
330
  foreach ($all_fields as $alias => $val) {
331
    $name = $alias;
332
    $tmp = explode('.', $name);
333
    $field_name = $tmp[1];
334
    $table_name = $tmp[0];
335
    
336
    // Skip unsupported field types and fields that weren't defined through
337
    // the Field module.
338
    $info = field_info_field($field_name);
339
    $supported_location_fields = array(
340
      'text',
341
      'text_long',
342
      'text_with_summary',
343
      'node_reference',
344
      'addressfield',
345
      'location'
346
    );
347
    if (!$info || !in_array($info['type'], $supported_location_fields)) {
348
      continue;
349
    }
350
    
351
    // Build an array of the field info that we'll need.
352
    $alias = str_replace('.', '_', $alias);
353
    $fields['name'][$name] = array(
354
      'label' => "{$val['group']}: {$val['title']} ($field_name)",
355
      'table_name' => $table_name,
356
      'field_name' => $field_name,
357
      'type' => $info['type'],
358
    );
359
    
360
    // These are here only to make this $field array conform to the same format
361
    // as the one returned by _date_views_fields(). They're probably not needed,
362
    // but I thought that consistency would be a good idea.
363
    $fields['name'][$name]['real_field_name'] = $field_name;
364
    $fields['alias'][$alias] = $fields['name'][$name];
365
  }
366
  
367
  cache_set($cid, $fields, 'cache_views');
368
  return $fields;
369
}
370

    
371

    
372
/**
373
 * Identify all potential fields which could be used as an iCal SUMMARY.
374
 */
375
function date_ical_get_summary_fields($base = 'node', $reset = FALSE) {
376
  static $fields = array();
377
  $empty = array('name' => array(), 'alias' => array());
378

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

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

    
409
  $cid = 'date_ical_summary_fields_' . $base;
410
  cache_clear_all($cid, 'cache_views');
411

    
412
  // Iterate over all the fields that Views knows about.
413
  $all_fields = date_views_views_fetch_fields($base, 'field');
414
  $fields = array();
415
  foreach ($all_fields as $alias => $val) {
416
    $name = $alias;
417
    $tmp = explode('.', $name);
418
    $field_name = $tmp[1];
419
    $table_name = $tmp[0];
420

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

    
435
    // Build an array of the field info that we'll need.
436
    $alias = str_replace('.', '_', $alias);
437
    $fields['name'][$name] = array(
438
      'label' => "{$val['group']}: {$val['title']} ($field_name)",
439
      'table_name' => $table_name,
440
      'field_name' => $field_name,
441
      'type' => $info['type'],
442
    );
443

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

    
451
  cache_set($cid, $fields, 'cache_views');
452
  return $fields;
453
}
454

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