Projet

Général

Profil

Paste
Télécharger (14,9 ko) Statistiques
| Branche: | Révision:

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

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.4-dev');
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. Paragraph,
226
 * heading, and div tags will be replaced with newlines.
227
 *
228
 * @param string $text
229
 *   The text to be sanitized.
230
 */
231
function date_ical_sanitize_text($text = '') {
232
  // HTML tags may get converted to &lt; and such by the View code, so we need
233
  // to convert them back to HTML so we can remove them with strip_tags().
234
  $text = decode_entities($text);
235
  
236
  // Convert <p> tags to double newlines.
237
  $text = trim(preg_replace("/<p.*?>/i", "\n\n", $text));
238
  // Separate heading tags from the text around them in both directions.
239
  $text = trim(preg_replace("/<\\?h\d.*?>/i", "\n\n", $text));
240
  // Add a newline for each <div>.
241
  $text = trim(preg_replace("/<div.*?>/i", "\n", $text));
242
  
243
  // Strip the remaining HTML.
244
  $text = strip_tags($text);
245
  // Remove newlines added at the beginning.
246
  return trim($text);
247
}
248

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

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

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

    
370

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

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

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

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

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

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

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

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

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

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