calendar_ical.module

Tracking 5.x-2.x branch
  1. drupal
    1. 5 contributions/calendar/calendar_ical.module
    2. 6 contributions/calendar/calendar_ical/calendar_ical.module

Adds ical functionality to Calendar.

Functions & methods

NameDescription
calendar_ical_add_feeds
calendar_ical_add_ical
calendar_ical_cacheIdentify the cache where the ical feeds are stored.
calendar_ical_calendar_add_itemsImplementation of hook_calendar_add_items().
calendar_ical_calendar_add_typesImplementation of hook_calendar_add_types().
calendar_ical_cronImplementation of hook_cron().
calendar_ical_menuImplementation of hook_menu().
calendar_ical_post_view_make_argshelper function -- this function builds a URL for a given feed. It defaults to the built in feed selector, but the 3rd arg can be used to set it up for custom selectors too.
calendar_ical_setup_form
calendar_ical_views_argumentsWhile we support the global selector, some might want to allow ONLY ical feeds so we support a stingy selector too
calendar_ical_views_feed_argumentfeed argument hook that will convert us to ical or display an icon. the 4th argument isn't part of the hook, but we use it to differentiate when called as a hook or when called manually from calendar_ical_views_post_view
calendar_ical_views_post_viewpost view for our own op -- mimics the feed selector
calendar_ical_views_style_pluginsProvide views plugins for the feed types we support.
theme_calendar_ical_feedplugin that actually displays an ical feed
theme_calendar_ical_fieldViews field theme for an ical field.
theme_calendar_ical_nodeTheme for an entire ical node.
theme_ical_icon
views_handler_arg_icalhandler for our own ical argument; mimics the feed selector

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Adds ical functionality to Calendar.
  5. */
  6. /**
  7. * Implementation of hook_menu().
  8. */
  9. function calendar_ical_menu($may_cache) {
  10. $items = array();
  11. if (!$may_cache) {
  12. drupal_add_css(drupal_get_path('module', 'calendar') .'/calendar.css');
  13. require_once('./'. drupal_get_path('module', 'calendar') .'/calendar.module');
  14. foreach (calendar_info() as $view_name => $view) {
  15. $items[] = array(
  16. 'path' => 'admin/settings/calendar/'. $view_name .'/ical',
  17. 'title' => t('iCal'),
  18. 'description' => t('iCal setup.'),
  19. 'access' => user_access('administer views'),
  20. 'callback' => 'drupal_get_form',
  21. 'callback arguments' => array('calendar_ical_setup_form', $view_name),
  22. 'type' => MENU_LOCAL_TASK,
  23. 'weight' => 6,
  24. );
  25. }
  26. }
  27. return $items;
  28. }
  29. function calendar_ical_setup_form($view_name) {
  30. require_once('./'. drupal_get_path('module', 'calendar') .'/calendar_ical_admin.inc');
  31. return _calendar_ical_setup_form($view_name);
  32. }
  33. /**
  34. * Identify the cache where the ical feeds are stored.
  35. *
  36. * @return unknown
  37. */
  38. function calendar_ical_cache() {
  39. return 'cache_views';
  40. }
  41. /**
  42. * Implementation of hook_cron().
  43. */
  44. function calendar_ical_cron() {
  45. cache_clear_all('calendar_feeds_', calendar_ical_cache(), TRUE);
  46. }
  47. /**
  48. * Implementation of hook_calendar_add_items().
  49. */
  50. function calendar_ical_calendar_add_items($view) {
  51. require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_ical.inc');
  52. $items = array();
  53. $feeds = calendar_ical_add_feeds($view);
  54. $min = date_format($view->min_date, DATE_FORMAT_DATETIME);
  55. $max = date_format($view->max_date, DATE_FORMAT_DATETIME);
  56. foreach ($feeds as $feed) {
  57. if (!empty($feed->calendar_start) && !empty($feed->calendar_end)) {
  58. if ($feed->calendar_start >= date_format($view->min_date, DATE_FORMAT_DATETIME) && $feed->calendar_end <= date_format($view->max_date, DATE_FORMAT_DATETIME)) {
  59. if (($feed->calendar_start >= $min && $feed->calendar_end <= $max) || ($feed->calendar_start >= $min && $feed->all_day == 1)){
  60. // We have to add a calendar start and end object to the feed,
  61. // the cached date objects will not work correctly.
  62. $feed->calendar_start_date = date_make_date($feed->calendar_start, $feed->timezone);
  63. $feed->calendar_end_date = date_make_date($feed->calendar_end, $feed->timezone);
  64. $items[] = $feed;
  65. }
  66. }
  67. }
  68. }
  69. return $items;
  70. }
  71. /**
  72. * Implementation of hook_calendar_add_types().
  73. */
  74. function calendar_ical_calendar_add_types($view) {
  75. return array('calendar_ical' => t('Calendar: iCal Feed'));
  76. }
  77. /**
  78. * Bring an ical feed into the calendar.
  79. *
  80. * @param $view - the view being manipulated
  81. * @param $refresh - whether or not to force a refresh of the feed
  82. * @return $nodes to add to calendar
  83. * @todo probably need to add more validation of the results in case the url doesn't work.
  84. */
  85. function calendar_ical_add_feeds($view, $refresh = FALSE) {
  86. $nodes = array();
  87. $feeds = variable_get('calendar_feeds_'. $view->name, array());
  88. $expire = intval(variable_get('calendar_ical_expire_'. $view->name, 9676800) + time());
  89. foreach ($feeds as $delta => $feed) {
  90. if ($view->build_type == 'page') {
  91. $GLOBALS['calendar_stripe_labels'][$feed['stripe']] = $feed['name'];
  92. }
  93. if (!$refresh && $cached = cache_get('calendar_feeds_'. $view->name .':'. $feed['url'], calendar_ical_cache())) {
  94. $nodes += (array) unserialize($cached->data);
  95. }
  96. else {
  97. $expire = intval(variable_get('calendar_ical_expire_'. $view->name, 9676800) + time());
  98. require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_ical.inc');
  99. switch ($feed['type']) {
  100. case 'ical':
  101. $filename = $feed['url'];
  102. $default = $feed['link'];
  103. $new_nodes = array();
  104. $calendars = date_ical_import($filename);
  105. if (empty($calendars)) {
  106. continue;
  107. }
  108. foreach ($calendars as $calendar) {
  109. if (empty($calendar) || empty($calendar['VEVENT'])) {
  110. continue;
  111. }
  112. foreach ($calendar['VEVENT'] as $key => $value) {
  113. if (empty($value['DTSTART'])) continue;
  114. $node = new stdClass();
  115. $node->nid = $value['UID'];
  116. $node->title = $value['SUMMARY'];
  117. $node->label = $feed['name'];
  118. $node->teaser = $value['DESCRIPTION'];
  119. $node->timezone = $value['DTSTART']['tz'];
  120. $node->calendar_start_date = date_ical_date($value['DTSTART'], $value['DTSTART']['tz']);
  121. $node->calendar_start = date_format_date($node->calendar_start_date, 'custom', DATE_FORMAT_DATETIME);
  122. $node->calendar_end_date = date_ical_date($value['DTEND'], $value['DTEND']['tz']);
  123. $node->calendar_end = date_format_date($node->calendar_end_date, 'custom', DATE_FORMAT_DATETIME);
  124. $show_tz = ' e';
  125. $granularity = $value['DTSTART']['type'] == 'DATE' ? array('year', 'month', 'day') : array('year', 'month', 'day', 'hour', 'minute', 'second');
  126. $node->format_time = variable_get('calendar_time_format_'. $view->name, 'H:i');
  127. $node->format = date_limit_format(variable_get('date_format_short', 'm/d/Y - H:i'), $granularity) . $show_tz;
  128. $node->all_day = $value['all_day'];
  129. $node->location = $value['LOCATION'];
  130. $node->stripe = $feed['stripe'];
  131. $node->stripe_label = $feed['name'];
  132. $node->remote = TRUE;
  133. $node->uid = $value['uid'] ? $value['UID'] : calendar_get_node_link($node, $default);
  134. $node->url = $value['URL'] ? $value['URL'] : calendar_get_node_link($node, $default);
  135. $node->calendar_node_theme = 'calendar_ical_node';
  136. $node->calendar_field_theme = 'calendar_ical_field';
  137. $new_nodes[$value['UID']] = $node;
  138. }
  139. }
  140. if (!empty($new_nodes)) {
  141. cache_set('calendar_feeds_'. $view->name .':'. $feed['url'], calendar_ical_cache(), serialize($new_nodes), $expire);
  142. $nodes += (array) $new_nodes;
  143. }
  144. break;
  145. }
  146. }
  147. }
  148. return $nodes;
  149. }
  150. /**
  151. * Provide views plugins for the feed types we support.
  152. */
  153. function calendar_ical_views_style_plugins() {
  154. return array(
  155. 'calendar_ical' => array(
  156. 'name' => t('Calendar: iCal Feed'),
  157. 'theme' => 'calendar_ical_feed',
  158. 'needs_table_header' => TRUE,
  159. 'needs_fields' => TRUE,
  160. 'even_empty' => TRUE,
  161. ),
  162. );
  163. }
  164. /**
  165. * While we support the global selector, some might want to allow
  166. * ONLY ical feeds so we support a stingy selector too
  167. */
  168. function calendar_ical_views_arguments() {
  169. $arguments = array(
  170. 'calendar_ical' => array(
  171. 'name' => t('Calendar: iCal Feed'),
  172. 'handler' => 'views_handler_arg_ical',
  173. 'help' => t('Add this as the last argument to a calendar view to provide an iCal feed of the view.'),
  174. ),
  175. );
  176. return $arguments;
  177. }
  178. /**
  179. * handler for our own ical argument; mimics the feed selector
  180. */
  181. function views_handler_arg_ical($op, &$query, $argtype, $arg = '') {
  182. switch ($op) {
  183. case 'summary':
  184. case 'sort':
  185. case 'link':
  186. case 'title':
  187. break;
  188. case 'filter':
  189. // This is a clone of the default selector, but it just invokes ours
  190. // rather than calling all of them.
  191. calendar_ical_views_feed_argument('argument', $GLOBALS['current_view'], $arg, $argtype);
  192. }
  193. }
  194. /**
  195. * post view for our own op -- mimics the feed selector
  196. */
  197. function calendar_ical_views_post_view($view, $items, $output) {
  198. foreach ($view->argument as $id => $argument) {
  199. if ($argument['type'] == 'calendar_ical') {
  200. $feed = $id;
  201. break;
  202. }
  203. }
  204. if ($feed !== NULL) {
  205. $query = NULL;
  206. return calendar_ical_views_feed_argument('post_view', $view, 'ical', $query);
  207. }
  208. }
  209. /**
  210. * feed argument hook that will convert us to ical or display an icon.
  211. * the 4th argument isn't part of the hook, but we use it to differentiate
  212. * when called as a hook or when called manually from calendar_ical_views_post_view
  213. */
  214. function calendar_ical_views_feed_argument($op, &$view, $arg, $argtype = NULL) {
  215. if ($op == 'argument' && $arg == 'ical') {
  216. // Keep devel module from appending queries to ical export.
  217. $GLOBALS['devel_shutdown'] = FALSE;
  218. $view->page_type = 'calendar_ical';
  219. // reset the 'real url' to the URL without the feed argument.
  220. $view_args = array();
  221. $max = count($view->args);
  222. foreach ($view->args as $id => $view_arg) {
  223. ++$count;
  224. if ($view_arg == $arg && $view->argument[$id]['id'] == $argtype['id']) {
  225. if ($count != $max) {
  226. $view_args[] = $argtype['wildcard'];
  227. }
  228. }
  229. else {
  230. $view_args[] = $view_arg;
  231. }
  232. }
  233. $url = calendar_get_url($view, $args, FALSE);
  234. $view->feed_url = $url;
  235. }
  236. else if ($op == 'post_view') {
  237. $args = calendar_ical_post_view_make_args($view, $arg, 'ical');
  238. $url = calendar_get_url($view, $args, FALSE);
  239. $title = views_get_title($view, 'page', $args);
  240. if ($view->used_filters) {
  241. $filters = drupal_query_string_encode($view->used_filters);
  242. }
  243. return implode(calendar_ical_add_ical(url($url, $filters), $title));
  244. }
  245. }
  246. /**
  247. * helper function -- this function builds a URL for a given feed.
  248. * It defaults to the built in feed selector, but the 3rd arg can
  249. * be used to set it up for custom selectors too.
  250. */
  251. function calendar_ical_post_view_make_args($view, $feed_id, $arg) {
  252. // assemble the URL
  253. $args = array();
  254. foreach ($view->argument as $id => $argdata) {
  255. if (isset($view->args[$id])) {
  256. $args[] = $view->args[$id];
  257. }
  258. else {
  259. if ($argdata['id'] == $feed_id && !in_array($arg, $args)) {
  260. $args[] = $arg;
  261. }
  262. else if ($argdata['argdefault'] != 1 && !in_array($arg, $args)) {
  263. $args[] = $arg;
  264. }
  265. }
  266. }
  267. return $args;
  268. }
  269. function calendar_ical_add_ical($url = NULL, $title = '') {
  270. if (!is_null($url)) {
  271. $stored_feed_links[$url] = theme('ical_icon', $url);
  272. drupal_add_link(array('rel' => 'alternate',
  273. 'type' => 'application/calendar',
  274. 'title' => $title,
  275. 'href' => $url));
  276. }
  277. return $stored_feed_links;
  278. }
  279. function theme_ical_icon($url) {
  280. if ($image = theme('image', drupal_get_path('module', 'date_api') .'/images/ical16x16.gif', t('Add to calendar'), t('Add to calendar'))) {
  281. return '<div style="text-align:right"><a href="'. check_url($url) .'" class="ical-icon" title="ical">'. $image .'</a></div>';
  282. }
  283. }
  284. /**
  285. * plugin that actually displays an ical feed
  286. */
  287. function theme_calendar_ical_feed($view, $items, $type) {
  288. if ($type == 'block') {
  289. return;
  290. }
  291. drupal_set_header('Content-Type: text/calendar; charset=utf-8');
  292. drupal_set_header('Content-Disposition: attachment; filename="calendar.ics"; ');
  293. $display_formats = variable_get('calendar_display_format_'. $view->name, array('year' => 'calendar', 'month' => 'calendar', 'week' => 'calendar', 'day' => 'calendar', 'block' => 'calendar'));
  294. $events = array();
  295. include_once('./'. drupal_get_path('module', 'date_api') .'/date_api_ical.inc');
  296. include_once('./'. drupal_get_path('module', 'calendar') .'/calendar.inc');
  297. $items = calendar_build_nodes($view, $items, $display_formats[$view->calendar_type]);
  298. foreach ($items as $time) {
  299. foreach ($time as $node) {
  300. $event = array();
  301. // Allow modules to affect item fields
  302. node_invoke_nodeapi($node, 'ical item');
  303. if (!$node->remote) {
  304. $real_node = node_load($node->nid);
  305. $node->teaser = $real_node->teaser;
  306. }
  307. $event['start'] = $node->calendar_start_date;
  308. $event['end'] = $node->calendar_end_date;
  309. $event['location'] = $node->calendar_location;
  310. $event['summary'] = $node->title;
  311. $event['description'] = $node->teaser;
  312. $event['url'] = $node->url ? $node->url : calendar_get_node_link($node);
  313. $event['uid'] = !empty($node->date_id) ? $node->date_id : $event['url'];
  314. $events[] = $event;
  315. }
  316. }
  317. $headertitle = filter_xss_admin(views_get_title($view, 'page'));
  318. $title = variable_get('site_name', 'Drupal');
  319. $description = $headertitle . ($title ? ' | '. $title : '');
  320. print date_ical_export($events, $description);
  321. module_invoke_all('exit');
  322. exit;
  323. }
  324. /**
  325. * Theme for an entire ical node.
  326. */
  327. function theme_calendar_ical_node($node, $view) {
  328. // Check plain may leave html entities in the title.
  329. $node->title = html_entity_decode(check_plain($node->title), ENT-QUOTES);
  330. $node->teaser = check_markup($node->teaser);
  331. if ($view->calendar_display == 'calendar') {
  332. // Remote nodes may come in with lengthy descriptions that won't fit
  333. // in small boxes of year, month, and week calendars.
  334. if ($view->calendar_type != 'day') {
  335. $node->teaser = '';
  336. }
  337. $theme = 'calendar_node_'. $view->calendar_type;
  338. return theme($theme, $node, $view);
  339. }
  340. else {
  341. // Create a pseudo node view for this item.
  342. $field = array();
  343. $output = '<div class="node">';
  344. $output .= '<h2>'. l($node->title, $node->url) .'</h2>';
  345. $output .= '<div>'. theme('calendar_date_combo', $field, $node, $node->format, t('Dates'), $view) .'</div>';
  346. $output .= '<div class="content">'. $node->teaser .'</div>';
  347. $output .= '</div>';
  348. return $output;
  349. }
  350. }
  351. /**
  352. * Views field theme for an ical field.
  353. *
  354. * Used for table and list views.
  355. *
  356. * For non-calendar views that use fields, the field needs a value from the
  357. * imported node that matches the kind of field in the view. Most possible
  358. * Views fields make no sense here and will return nothing.
  359. */
  360. function theme_calendar_ical_field($fieldname, $fields, $field, $node, $view, $type) {
  361. static $datefield;
  362. // Find the first date field in the view to attach the ical date to.
  363. // There may be more than one date field in the view and we only
  364. // want to put the ical field in one place.
  365. if (empty($datefield)) {
  366. $calendar_fields = calendar_fields();
  367. foreach ($view->field as $viewfield) {
  368. if (in_array($viewfield['field'], array_keys($calendar_fields))) {
  369. $datefield = $viewfield['queryname'];
  370. break;
  371. }
  372. }
  373. }
  374. // Check plain may leave html entities in the title.
  375. $node->title = html_entity_decode(check_plain($node->title), ENT-QUOTES);
  376. $node->teaser = check_markup($node->teaser);
  377. // Fix some common html entities that may not get decoded correctly.
  378. // You can add more of them in this array.
  379. $replace = array(
  380. '&#039;' => "'",
  381. );
  382. foreach (array('title', 'teaser') as $key) {
  383. $node->$key = strtr($node->$key, $replace);
  384. }
  385. if ($fieldname == $datefield) {
  386. return theme('calendar_date_combo', $field, $node, '', $view);
  387. }
  388. switch ($field['fullname']) {
  389. case 'node.title':
  390. return l($node->title, $node->url);
  391. case 'node.teaser':
  392. case 'node.body':
  393. return $node->teaser;
  394. case 'node.type':
  395. return $node->label;
  396. }
  397. return;
  398. }