fullcalendar.module 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. <?php
  2. /**
  3. * @file
  4. * Provides a views style plugin for FullCalendar
  5. */
  6. /**
  7. * The default path to the FullCalendar plugin.
  8. */
  9. function fullcalendar_default_path() {
  10. return module_exists('libraries') ? libraries_get_path('fullcalendar') : 'sites/all/libraries/fullcalendar';
  11. }
  12. /**
  13. * The minimum supported version of the FullCalendar plugin.
  14. */
  15. define('FULLCALENDAR_MIN_PLUGIN_VERSION', '1.4.10');
  16. /**
  17. * The recommended version of the FullCalendar plugin.
  18. */
  19. define('FULLCALENDAR_RECOMMENDED_PLUGIN_VERSION', '1.5.1');
  20. /**
  21. * Implements hook_views_api().
  22. */
  23. function fullcalendar_views_api() {
  24. return array(
  25. 'api' => '3',
  26. 'path' => drupal_get_path('module', 'fullcalendar') . '/includes/views',
  27. );
  28. }
  29. /**
  30. * Implements hook_theme().
  31. */
  32. function fullcalendar_theme($existing, $type, $theme, $path) {
  33. return array(
  34. 'fullcalendar_event' => array(
  35. 'variables' => array(
  36. 'event' => NULL,
  37. 'entity' => NULL,
  38. ),
  39. 'file' => 'theme.inc',
  40. 'path' => $path . '/theme',
  41. ),
  42. );
  43. }
  44. /**
  45. * Implements hook_library().
  46. */
  47. function fullcalendar_library() {
  48. $path = variable_get('fullcalendar_path', fullcalendar_default_path());
  49. $css_files = array($path . '/fullcalendar.css' => array('media' => 'screen'));
  50. // Add fullcalendar.print.css if it is available.
  51. if (version_compare(fullcalendar_get_version(), '1.5.0', '>=')) {
  52. $css_files[$path . '/fullcalendar.print.css'] = array('media' => 'print');
  53. }
  54. $libraries['fullcalendar'] = array(
  55. 'title' => 'FullCalendar',
  56. 'website' => 'http://arshaw.com/fullcalendar',
  57. 'version' => FULLCALENDAR_MIN_PLUGIN_VERSION,
  58. 'js' => array(
  59. fullcalendar_get_js_path() => array(),
  60. $path . '/gcal.js' => array(),
  61. ),
  62. 'css' => $css_files,
  63. 'dependencies' => array(
  64. array('system', 'ui.draggable'),
  65. array('system', 'ui.droppable'),
  66. array('system', 'ui.resizable'),
  67. array('system', 'effects.highlight'),
  68. ),
  69. );
  70. return $libraries;
  71. }
  72. /**
  73. * Implements hook_permission().
  74. *
  75. * @return array
  76. * An array of valid permissions for the FullCalendar module.
  77. */
  78. function fullcalendar_permission() {
  79. return array(
  80. 'update any fullcalendar event' => array(
  81. 'title' => t('Update any FullCalendar event'),
  82. 'description' => t('Allow user to edit events, ignoring other permissions.'),
  83. ),
  84. );
  85. }
  86. /**
  87. * Implements hook_menu().
  88. *
  89. * @return array
  90. * An array of menu items.
  91. */
  92. function fullcalendar_menu() {
  93. $items = array();
  94. $items['admin/config/user-interface/fullcalendar'] = array(
  95. 'title' => 'FullCalendar',
  96. 'description' => 'Adjust FullCalendar settings.',
  97. 'file' => 'fullcalendar.admin.inc',
  98. 'file path' => drupal_get_path('module', 'fullcalendar') . '/includes',
  99. 'page callback' => 'drupal_get_form',
  100. 'page arguments' => array('fullcalendar_admin_settings'),
  101. 'access arguments' => array('administer site configuration'),
  102. 'type' => MENU_NORMAL_ITEM,
  103. 'weight' => 0,
  104. );
  105. $items['admin/config/user-interface/fullcalendar/default'] = array(
  106. 'title' => 'FullCalendar',
  107. 'type' => MENU_DEFAULT_LOCAL_TASK,
  108. 'weight' => 0,
  109. );
  110. $items['fullcalendar/ajax/update/%/%'] = array(
  111. 'title' => 'Update event',
  112. 'description' => 'Save the updated event datetime.',
  113. 'page callback' => 'fullcalendar_update',
  114. 'page arguments' => array(3, 4),
  115. 'access callback' => '_fullcalendar_update_access',
  116. 'access arguments' => array(4),
  117. 'type' => MENU_CALLBACK,
  118. );
  119. $items['fullcalendar/ajax/results'] = array(
  120. 'title' => 'Events',
  121. 'description' => 'Get events from views with arguments',
  122. 'page callback' => 'fullcalendar_results',
  123. 'access arguments' => array('access content'),
  124. 'delivery callback' => 'ajax_deliver',
  125. 'theme callback' => 'ajax_base_page_theme',
  126. 'type' => MENU_CALLBACK,
  127. );
  128. return $items;
  129. }
  130. /**
  131. * Saves the updated FullCalendar event's datetime.
  132. *
  133. * @param string $action
  134. * Value can be 'drop' or 'resize'.
  135. * @param int $eid
  136. * The id of the entity that will be updated.
  137. */
  138. function fullcalendar_update($action, $eid) {
  139. if (empty($_POST['field']) || !isset($_POST['index'])) {
  140. return;
  141. }
  142. // Retrieve the post vars form the ajax call.
  143. $field = check_plain($_POST['field']);
  144. $index = check_plain($_POST['index']);
  145. $all_day = isset($_POST['all_day']) ? check_plain($_POST['all_day']) : '';
  146. $delta = ' ' . check_plain($_POST['day_delta']) . ' days ' . check_plain($_POST['minute_delta']) . ' minutes';
  147. $entity_type = check_plain($_POST['entity_type']);
  148. $entity = entity_load($entity_type, array($eid));
  149. $entity = reset($entity);
  150. $langcode = field_language($entity_type, $entity, $field);
  151. $item = &$entity->{$field}[$langcode][$index];
  152. $old_start = $item['value'];
  153. $old_end = $item['value2'];
  154. // Adjust for different date formats.
  155. $format = date_type_format($item['date_type']);
  156. // Datestamp can't combine with words for strtotime, convert to ISO for now.
  157. if ($format == DATE_FORMAT_UNIX) {
  158. $old_start = date(DATE_FORMAT_ISO, $old_start);
  159. $old_end = date(DATE_FORMAT_ISO, $old_end);
  160. }
  161. // No break after 'drop' so it will reuse the code of 'resize'.
  162. switch ($action) {
  163. case 'drop':
  164. $item['value'] = date($format, strtotime($old_start . $delta));
  165. case 'resize':
  166. $item['value2'] = date($format, strtotime($old_end . $delta));
  167. break;
  168. }
  169. // Save the new start/end values.
  170. if (module_exists('entity')) {
  171. entity_save($entity_type, $entity);
  172. }
  173. else {
  174. field_attach_presave($entity_type, $entity);
  175. field_attach_update($entity_type, $entity);
  176. }
  177. drupal_json_output(array('msg' => t('The new event time has been saved.') . ' [' . l(t('Close'), NULL, array('attributes' => array('class' => array('fullcalendar-status-close')))) . ']', 'dom_id' => $_POST['dom_id']));
  178. }
  179. /**
  180. * Implements hook_fullcalendar_classes().
  181. */
  182. function fullcalendar_fullcalendar_classes($entity) {
  183. $classes = array(
  184. 'fc-event-default',
  185. $entity->bundle,
  186. );
  187. // Add a class for the date field being used.
  188. if (isset($entity->fullcalendar_date_field)) {
  189. $classes[] = "fc-event-field-$entity->fullcalendar_date_field";
  190. }
  191. return $classes;
  192. }
  193. /**
  194. * Implements hook_form_FORM_ID_alter() for views_ui_edit_display_form().
  195. *
  196. * Since we force the query to be distinct, reflect that in the UI.
  197. */
  198. function fullcalendar_form_views_ui_edit_display_form_alter(&$form, &$form_state, $form_id) {
  199. if ($form_state['view']->display_handler->get_option('style_plugin') != 'fullcalendar' || empty($form['options']['query']['options']['distinct'])) {
  200. return;
  201. }
  202. $distinct = &$form['options']['query']['options']['distinct'];
  203. if (!isset($distinct['#description'])) {
  204. $distinct['#description'] = '';
  205. }
  206. else {
  207. $distinct['#description'] .= '<br>';
  208. }
  209. $distinct['#disabled'] = TRUE;
  210. $distinct['#description'] .= '<strong>' . t('FullCalendar requires that the query be distinct.') . '</strong>';
  211. }
  212. /**
  213. * Returns events for FullCalendar.
  214. */
  215. function fullcalendar_results($view_name = NULL, $view_display = NULL) {
  216. // Bail out if no view_name or view_display is passed.
  217. if (empty($view_name) || empty($view_display) || empty($_POST['dom_id'])) {
  218. return;
  219. }
  220. // Find Views arguments.
  221. $args = func_get_args();
  222. unset($args[0], $args[1]);
  223. $args = array_values($args);
  224. $dom_id = check_plain($_POST['dom_id']);
  225. $view_name = check_plain($view_name);
  226. $view_display = check_plain($view_display);
  227. // Add all $_POST data, because AJAX is always a post and many things,
  228. // such as tablesorts, exposed filters and paging assume $_GET.
  229. $_GET += $_POST;
  230. // Get the view and check access.
  231. $view = views_get_view($view_name);
  232. if (!$view || !$view->access($view_display)) {
  233. return;
  234. }
  235. if (!$view->set_display($view_display)) {
  236. return;
  237. }
  238. $view->dom_id = $dom_id;
  239. $view->fullcalendar_ajax = TRUE;
  240. $view->pre_execute($args);
  241. $view->init_style();
  242. $view->execute($view_display);
  243. $output = $view->style_plugin->render();
  244. $view->post_execute();
  245. return array(
  246. '#type' => 'ajax',
  247. '#commands' => array(
  248. array('command' => 'fullcalendar_results_response', 'data' => $output),
  249. ),
  250. );
  251. }
  252. /**
  253. * Checks if the user has access to update the given FullCalendar event.
  254. *
  255. * @param object $entity
  256. * The entity that will be updated.
  257. *
  258. * @return bool
  259. * TRUE if the user is allowed to update the entity;
  260. * FALSE if the user is not permitted to update the entity.
  261. */
  262. function _fullcalendar_update_access($entity) {
  263. global $user;
  264. if (!empty($entity) && ((user_access('administer content')
  265. || user_access('update any fullcalendar event')
  266. || user_access('edit any ' . $entity->bundle . ' content')
  267. || (user_access('edit own ' . $entity->bundle . ' content') && $entity->uid == $user->uid)))) {
  268. return TRUE;
  269. }
  270. return FALSE;
  271. }
  272. /**
  273. * Implements hook_fullcalendar_editable().
  274. *
  275. * Use our access callback as the editable setting.
  276. */
  277. function fullcalendar_fullcalendar_editable($entity, $view) {
  278. return _fullcalendar_update_access($entity);
  279. }
  280. /**
  281. * Determines if a given field is a date field.
  282. *
  283. * @param object $field
  284. * A Views field handler object.
  285. * @param bool $include_gcal
  286. * Boolean indicating whether or not to count gcal fields as a date field.
  287. *
  288. * @return bool
  289. * Boolean, TRUE if the field is a date field, FALSE otherwise.
  290. */
  291. function fullcalendar_field_is_date($field, $include_gcal = FALSE) {
  292. if ($include_gcal && $field->field == 'gcal') {
  293. return TRUE;
  294. }
  295. return !empty($field->definition['is date']) && isset($field->field_info);
  296. }
  297. /**
  298. * Checks FullCalendar dependencies.
  299. *
  300. * @return bool
  301. * Array with TRUE/FALSE for each dependency.
  302. *
  303. * @see fullcalendar_requirements()
  304. */
  305. function _fullcalendar_status() {
  306. $status = array();
  307. $status['fullcalendar_plugin'] = FALSE;
  308. $path = fullcalendar_get_js_path();
  309. if (!file_exists($path)) {
  310. return FALSE;
  311. }
  312. if (version_compare(fullcalendar_get_version($path), FULLCALENDAR_MIN_PLUGIN_VERSION, '>=')) {
  313. $status['fullcalendar_plugin'] = TRUE;
  314. }
  315. return $status;
  316. }
  317. /**
  318. * Returns the version of FullCalendar plugin that is installed.
  319. *
  320. * This can be used by other modules' hook_requirements() to ensure that the
  321. * proper version of FullCalendar plugin is installed.
  322. *
  323. * @see version_compare()
  324. */
  325. function fullcalendar_get_version($fullcalendar_path = NULL) {
  326. $version = &drupal_static(__FUNCTION__);
  327. if (empty($version)) {
  328. $version = 0;
  329. $pattern = '#FullCalendar v([0-9\.a-z]+)#';
  330. // No file is passed in so use the default location.
  331. if (is_null($fullcalendar_path)) {
  332. $fullcalendar_path = fullcalendar_get_js_path();
  333. }
  334. // Return the version of FullCalendar plugin.
  335. $fullcalendar_plugin = file_get_contents($fullcalendar_path, NULL, NULL, 0, 40);
  336. if (preg_match($pattern, $fullcalendar_plugin, $matches)) {
  337. $version = $matches[1];
  338. }
  339. }
  340. return $version;
  341. }
  342. /**
  343. * Returns the path to the FullCalendar plugin.
  344. */
  345. function fullcalendar_get_js_path() {
  346. $fullcalendar_file = array('none' => 'fullcalendar.js', 'min' => 'fullcalendar.min.js');
  347. return variable_get('fullcalendar_path', fullcalendar_default_path()) . '/' . $fullcalendar_file[variable_get('fullcalendar_compression_type', 'min')];
  348. }
  349. /**
  350. * Includes all FullCalendar API plugins.
  351. */
  352. function fullcalendar_include_api() {
  353. ctools_include('plugins');
  354. return ctools_plugin_api_include('fullcalendar', 'fullcalendar', fullcalendar_api_version(), fullcalendar_api_minimum_version());
  355. }
  356. /**
  357. * Implements hook_ctools_plugin_api_hook_name().
  358. */
  359. function fullcalendar_ctools_plugin_api_hook_name() {
  360. return 'fullcalendar_api';
  361. }
  362. /**
  363. * Declares the current FullCalendar API version.
  364. */
  365. function fullcalendar_api_version() {
  366. return '1';
  367. }
  368. /**
  369. * Declares the minimum FullCalendar API version.
  370. */
  371. function fullcalendar_api_minimum_version() {
  372. return '1';
  373. }
  374. /**
  375. * Implements hook_fullcalendar_api().
  376. */
  377. function fullcalendar_fullcalendar_api() {
  378. return array(
  379. 'api' => fullcalendar_api_version(),
  380. 'path' => drupal_get_path('module', 'fullcalendar') . '/includes',
  381. );
  382. }