fixed spelling
[project/section.git] / section.module
1 <?php
2 /**
3 * @file
4 */
5
6 if (module_exists('menu')) {
7 require_once (dirname(__FILE__) . '/section.menu.inc');
8 }
9
10 if (module_exists('path')) {
11 require_once (dirname(__FILE__) . '/section.path.inc');
12 }
13
14
15 /**
16 * Implements hook_entity_info().
17 */
18 function section_entity_info() {
19 $definition = array(
20 'section' => array(
21 'label' => t('Section'),
22 'entity class' => 'Entity',
23 'controller class' => 'EntityAPIControllerExportable',
24 'fieldable' => TRUE,
25 'exportable' => TRUE,
26 'uri callback' => 'section_uri',
27 'static cache' => TRUE,
28 'view modes' => array(
29 'full' => array(
30 'label' => t('Full content'),
31 'custom settings' => FALSE,
32 ),
33 ),
34 'access callback' => 'section_access',
35 'base table' => 'section',
36 'revision table' => 'section_revision',
37 'module' => 'section',
38 'bundle keys' => array(
39 'bundle' => 'name',
40 ),
41 'bundles' => array(),
42 'entity keys' => array(
43 'id' => 'sid',
44 'name' => 'name',
45 'bundle' => 'bundle',
46 'revision' => 'rid',
47 'label' => 'label',
48 ),
49 ),
50 );
51
52 $bundles = section_bundles();
53 foreach ($bundles as $bundle) {
54 $definition['section']['bundles'][$bundle->name] = array(
55 'label' => $bundle->label,
56 'admin' => array(
57 'path' => 'admin/structure/section_bundle/list/%section_bundle/edit',
58 'real path' => 'admin/structure/section_bundle/list/' . $bundle->name . '/edit',
59 'bundle argument' => 4,
60 'access arguments' => array('administer section bundles'),
61 ),
62 );
63 }
64
65 return $definition;
66 }
67
68 /**
69 * Menu loader callback; Load a section bundle object.
70 */
71 function section_bundle_load($name) {
72 $bundles = section_bundles();
73 return isset($bundles[$name]) ? $bundles[$name] : FALSE;
74 }
75 /**
76 * Implements hook_permission().
77 */
78 function section_permission() {
79 $perms = array(
80 'administer section bundles' => array(
81 'title' => t('Administer section bundles'),
82 'restrict access' => TRUE,
83 ),
84 'create section bundles' => array(
85 'title' => t('Create section bundles'),
86 ),
87 'delete section bundles' => array(
88 'title' => t('Delete section bundles'),
89 ),
90 'administer sections' => array(
91 'title' => t('Administer sections'),
92 'restrict access' => TRUE,
93 ),
94 'access section overview' => array(
95 'title' => t('Access the section overview page'),
96 ),
97 'access section' => array(
98 'title' => t('View sections'),
99 ),
100 );
101
102 // Generate standard section permissions for all applicable section bundles.
103 foreach (section_bundles() as $bundle) {
104 $perms += section_list_permissions($bundle);
105 }
106
107 return $perms;
108 }
109
110 /**
111 * Helper function to generate standard asection permission list for a given
112 * bundle.
113 *
114 * @param $bundle
115 * The section bundle.
116 * @return array
117 * An array of permission names and descriptions.
118 */
119 function section_list_permissions($bundle) {
120 $bundle_name = check_plain($bundle->name);
121
122 // Build standard list of node permissions for this type.
123 $perms = array(
124 "create $bundle_name section" => array(
125 'title' => t('%bundle_label: Create new sections', array('%bundle_label' => $bundle->label)),
126 ),
127 "edit any $bundle_name section" => array(
128 'title' => t('%bundle_label: Edit any sections', array('%bundle_label' => $bundle->label)),
129 ),
130 "delete any $bundle_name section" => array(
131 'title' => t('%bundle_label: Delete any sections', array('%bundle_label' => $bundle->label)),
132 ),
133 );
134
135 return $perms;
136 }
137
138 /**
139 * Access callback.
140 */
141 function section_access($op, $section, $account = NULL) {
142 $rights = &drupal_static(__FUNCTION__, array());
143
144 if (!$section || !in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
145 // If there was no section to check against, or the $op was not one of the
146 // supported ones, we return access denied.
147 return FALSE;
148 }
149 // If no user object is supplied, the access check is for the current user.
150 if (empty($account)) {
151 $account = $GLOBALS['user'];
152 }
153
154 if (in_array($op, array('view', 'update', 'delete', 'create'), TRUE) && user_access('administer sections', $account)) {
155 return TRUE;
156 }
157
158 // $section may be either an object or a section bundle. Since section bundles
159 // cannot be an integer, use either sid or bundle as the static cache id.
160
161 $cid = is_object($section) ? $section->name : $section;
162
163 // If we've already checked access for this section, user and op, return from
164 // cache.
165 if (isset($rights[$account->uid][$cid][$op])) {
166 return $rights[$account->uid][$cid][$op];
167 }
168
169 if (!user_access('access section', $account)) {
170 $rights[$account->uid][$cid][$op] = FALSE;
171 return FALSE;
172 }
173
174 $type = is_string($section) ? $section : $section->bundle;
175
176 $bundles = array();
177 foreach(section_bundles() as $bundle_name => $bundle) {
178 $bundles[] = $bundle_name;
179 }
180
181 if (in_array($type, $bundles)) {
182 if ($op == 'create' && user_access('create ' . $type . ' section', $account)) {
183 return 'TRUE';
184 }
185
186 if ($op == 'update') {
187 if (user_access('edit any ' . $type . ' section', $account)) {
188 return TRUE;
189 }
190 }
191
192 if ($op == 'delete') {
193 if (user_access('delete any ' . $type . ' section', $account)) {
194 return TRUE;
195 }
196 }
197 }
198
199 if ($op != 'create' && $section->name) {
200 if (is_object($section) && $op == 'view') {
201 $rights[$account->uid][$cid][$op] = TRUE;
202 return TRUE;
203 }
204 }
205
206 return FALSE;
207 }
208
209 function _section_add_access() {
210 $bundles = section_bundles();
211 foreach ($bundles as $bundle) {
212 if (section_access('create', $bundle->name)) {
213 return TRUE;
214 }
215 }
216 if (user_access('administer section bundles')) {
217 return TRUE;
218 }
219 return FALSE;
220 }
221
222 /**
223 * Implements hook_ctools_plugin_directory().
224 */
225 function section_ctools_plugin_directory($module, $type) {
226 $types = array(
227 'export_ui',
228 'access',
229 'tasks',
230 );
231 if (in_array($type, $types)) {
232 return 'plugins/' . $type;
233 }
234 }
235
236 /**
237 * Fetch all bundles for a section.
238 * @return array
239 * An array of all available bundles.
240 */
241 function section_bundles() {
242 ctools_include('export');
243 return ctools_export_load_object('section_bundle');
244 }
245
246 /**
247 * Implements hook_menu().
248 */
249 function section_menu() {
250 $items['section/autocomplete'] = array(
251 'title' => 'Section autocomplete',
252 'page callback' => 'section_autocomplete',
253 'access callback' => 'section_access',
254 'access arguments' => array('access section'),
255 'type' => MENU_CALLBACK,
256 );
257
258 $items['admin/section'] = array(
259 'title' => 'Section',
260 'description' => 'Find and manage sections.',
261 'page callback' => 'drupal_get_form',
262 'page arguments' => array('section_admin_section'),
263 'access arguments' => array('access section overview'),
264 'weight' => -10,
265 'file' => 'section.admin.inc',
266 );
267
268 $items['admin/section/section'] = array(
269 'title' => 'Section',
270 'type' => MENU_DEFAULT_LOCAL_TASK,
271 'weight' => -10,
272 );
273
274 $items['section/add'] = array(
275 'title' => 'Add section',
276 'page callback' => 'section_add_page',
277 'access callback' => '_section_add_access',
278 'file' => 'section.pages.inc',
279 );
280
281 $bundles = section_bundles();
282 $bundle_info = array();
283 foreach ($bundles as $key => $bundle) {
284 $items['section/add/' . $bundle->name] = array(
285 'title' => $bundle->label,
286 'title callback' => 'check_plain',
287 'page callback' => 'section_add',
288 'page arguments' => array($bundle->name),
289 'access callback' => 'section_access',
290 'access arguments' => array('create', $bundle->name),
291 'description' => $bundle->description,
292 'file' => 'section.pages.inc',
293 );
294 }
295
296 $items['section/%section'] = array(
297 'title callback' => 'section_page_title',
298 'title arguments' => array(1),
299 // The page callback also invokes drupal_set_title() in case
300 // the menu router's title is overridden by a menu link.
301 'page callback' => 'section_page_view',
302 'page arguments' => array(1),
303 'access callback' => 'section_access',
304 'access arguments' => array('view', 1),
305 );
306 $items['section/%section/view'] = array(
307 'title' => 'View',
308 'type' => MENU_DEFAULT_LOCAL_TASK,
309 'weight' => -10,
310 );
311 $items['section/%section/edit'] = array(
312 'title' => 'Edit',
313 'page callback' => 'section_page_edit',
314 'page arguments' => array(1),
315 'access callback' => 'section_access',
316 'access arguments' => array('update', 1),
317 'weight' => 0,
318 'type' => MENU_LOCAL_TASK,
319 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
320 'file' => 'section.pages.inc',
321 );
322 $items['section/%section/delete'] = array(
323 'title' => 'Delete',
324 'page callback' => 'drupal_get_form',
325 'page arguments' => array('section_delete_confirm', 1),
326 'access callback' => 'section_access',
327 'access arguments' => array('delete', 1),
328 'weight' => 1,
329 'type' => MENU_LOCAL_TASK,
330 'context' => MENU_CONTEXT_INLINE,
331 'file' => 'section.pages.inc',
332 );
333
334 return $items;
335 }
336
337 /**
338 * Implements hook_menu_local_tasks_alter().
339 */
340 function section_menu_local_tasks_alter(&$data, $router_item, $root_path) {
341 // Add action link to 'section/add' on 'admin/section' page.
342 if ($root_path == 'admin/section') {
343 $item = menu_get_item('section/add');
344 if ($item['access']) {
345 $data['actions']['output'][] = array(
346 '#theme' => 'menu_local_action',
347 '#link' => $item,
348 );
349 }
350 }
351 }
352
353 /**
354 * Implements hook_admin_paths().
355 */
356 function section_admin_paths() {
357 // We can use the same variable as the node module here.
358 if (variable_get('node_admin_theme')) {
359 $paths = array(
360 'section/*/edit' => TRUE,
361 'section/*/delete' => TRUE,
362 'section/add' => TRUE,
363 'section/add/*' => TRUE,
364 );
365 return $paths;
366 }
367 }
368
369 /**
370 * Implements hook_theme().
371 */
372 function section_theme() {
373 return array(
374 'section_add_list' => array(
375 'variables' => array('bundles' => NULL),
376 'file' => 'section.pages.inc',
377 ),
378 );
379 }
380
381 /**
382 * Add default section body field to a section bundle.
383 *
384 * @param $bundle
385 * A section bundle object.
386 * @param $label
387 * The label for the body instance.
388 *
389 * @return
390 * Body field instance.
391 */
392 function section_bundle_add_body_field($bundle, $label = 'Body') {
393 // Add or remove the body field, as needed.
394 $field = field_info_field('section_body');
395 $instance = field_info_instance('section', 'section_body', $bundle->name);
396 if (empty($field)) {
397 $field = array(
398 'field_name' => 'section_body',
399 'type' => 'text_with_summary',
400 'entity_types' => array('section'),
401 );
402 $field = field_create_field($field);
403 }
404 if (empty($instance)) {
405 $instance = array(
406 'field_name' => 'section_body',
407 'entity_type' => 'section',
408 'bundle' => $bundle->name,
409 'label' => $label,
410 'widget' => array('type' => 'text_textarea_with_summary'),
411 'settings' => array('display_summary' => TRUE),
412 'display' => array(
413 'default' => array(
414 'label' => 'hidden',
415 'type' => 'text_default',
416 ),
417 ),
418 );
419 $instance = field_create_instance($instance);
420 }
421 return $instance;
422 }
423
424 /**
425 * Implements hook_forms().
426 * All sections forms share the same form handler.
427 */
428 function section_forms() {
429 $forms = array();
430 if ($bundles = section_bundles()) {
431 foreach (array_keys($bundles) as $bundle) {
432 $forms[$bundle . '_section_form']['callback'] = 'section_form';
433 }
434 }
435 return $forms;
436 }
437
438 /**
439 * Entity uri callback.
440 */
441 function section_uri($section) {
442 return array(
443 'path' => 'section/' . $section->name,
444 );
445 }
446
447 /**
448 * Title callback.
449 * @TODO: Should we use this?
450 */
451 function section_page_title($section) {
452 return $section->label;
453 }
454
455 /**
456 * Menu callback; view a single section.
457 */
458 function section_page_view($section) {
459 // If there is a menu link to this section, the link becomes the last part
460 // of the active trail, and the link name becomes the page title.
461 // Thus, we must explicitly set the page title to be the section title.
462 drupal_set_title($section->label);
463 $uri = entity_uri('section', $section);
464 // Set the section path as the canonical URL to prevent duplicate content.
465 drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'], $uri['options'])), TRUE);
466 // Set the non-aliased path as a default shortlink.
467 drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
468 return section_show($section);
469 }
470
471 /**
472 * Generate an array which displays a section detail page.
473 *
474 * @param $section
475 * A node object.
476 * @return
477 * A $page element suitable for use by drupal_page_render().
478 */
479 function section_show($section) {
480 $sections = section_view_multiple(array($section->sid => $section), 'full');
481 return $sections;
482 }
483
484 /**
485 * Construct a drupal_render() style array from an array of loaded sections.
486 *
487 * @param $sections
488 * An array of sections as returned by section_view_multiple().
489 * @param $view_mode
490 * View mode, e.g. 'full', 'teaser'...
491 * @param $weight
492 * An integer representing the weight of the first node in the list.
493 * @param $langcode
494 * (optional) A language code to use for rendering. Defaults to the global
495 * content language of the current request.
496 *
497 * @return
498 * An array in the format expected by drupal_render().
499 */
500 function section_view_multiple($sections, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
501 field_attach_prepare_view('section', $sections, $view_mode, $langcode);
502 entity_prepare_view('section', $sections, $langcode);
503 $build = array();
504 foreach ($sections as $section) {
505 $build['sections'][$section->name] = section_view($section, $view_mode, $langcode);
506 $build['sections'][$section->name]['#weight'] = $weight;
507 $weight++;
508 }
509 $build['sections']['#sorted'] = TRUE;
510 return $build;
511 }
512
513 /**
514 * Generate an array for rendering the given node.
515 */
516 function section_view($section, $view_mode = 'full', $langcode = NULL) {
517 if (!isset($langcode)) {
518 $langcode = $GLOBALS['language_content']->language;
519 }
520
521 $section->content = field_attach_view('section', $section, $view_mode, $langcode);
522
523 $build = $section->content;
524 // We don't need duplicate rendering info in section->content.
525 unset($section->content);
526
527 $build += array(
528 '#theme' => 'section',
529 '#node' => $section,
530 '#view_mode' => $view_mode,
531 '#language' => $langcode,
532 );
533
534 // Add contextual links for this node, except when the node is already being
535 // displayed on its own page. Modules may alter this behavior (for example,
536 // to restrict contextual links to certain view modes) by implementing
537 // hook_node_view_alter().
538 /*
539 if (!empty($node->nid) && !($view_mode == 'full' && node_is_page($node))) {
540 $build['#contextual_links']['node'] = array('node', array($node->nid));
541 }
542 */
543
544 return $build;
545 }
546
547 /**
548 * Load node entities from the database.
549 */
550 function section_load_multiple($names = array(), $conditions = array(), $reset = FALSE) {
551 return entity_load_multiple_by_name('section', $names, $conditions);
552 }
553
554 /**
555 * Load a section object from the database.
556 */
557 function section_load($name = NULL) {
558 $names = (isset($name) ? array($name) : array());
559 $sections = section_load_multiple($names);
560 return $sections ? reset($sections) : FALSE;
561 }
562
563 /**
564 * Returns the section bundle of the passed section or bundle string.
565 */
566 function section_bundle_get_name($section) {
567 $bundle_name = _section_extract_bundle($section);
568 $bundle = section_bundle_load($bundle_name);
569
570 return isset($bundle->label) ? $bundle->label : FALSE;
571 }
572
573 /**
574 * Extract the bundle name.
575 */
576 function _section_extract_bundle($section) {
577 return is_object($section) ? $section->bundle : $section;
578 }
579
580 /**
581 * Delete a section.
582 *
583 * @param $sid
584 * A section ID.
585 */
586 function section_delete($sid) {
587 section_delete_multiple(array($sid));
588 }
589
590 /**
591 * Delete multiple sections.
592 *
593 * @param $sids
594 * An array of section IDs.
595 */
596 function section_delete_multiple($sids) {
597 $transaction = db_transaction();
598 if (!empty($sids)) {
599 $sections = section_load_multiple($sids, array());
600
601 try {
602 foreach ($sections as $sid => $section) {
603 field_attach_delete('section', $section);
604 }
605 entity_get_controller('section')->delete($sids);
606 }
607 catch (Exception $e) {
608 $transaction->rollback();
609 watchdog_exception('section', $e);
610 throw $e;
611 }
612
613 // Clear the entity cache.
614 entity_get_controller('section')->resetCache();
615 }
616 }
617
618 /**
619 *
620 */
621 function section_entity_insert($entity, $type) {
622 if ($type !== 'section') {
623 return;
624 }
625
626 if (module_exists('path')) {
627 if (isset($entity->path)) {
628 $path = $entity->path;
629 $path['alias'] = trim($path['alias']);
630 // Only save a non-empty alias.
631 if (!empty($path['alias'])) {
632 // Ensure fields for programmatic executions.
633 $path['source'] = 'section/' . $entity->sid;
634 $path['language'] = LANGUAGE_NONE;
635 path_save($path);
636 }
637 }
638 }
639
640 if (module_exists('pathauto')) {
641 section_update_alias($entity, 'insert');
642 }
643 }
644
645 /**
646 *
647 */
648 function section_entity_update($entity, $type) {
649 if ($type !== 'section') {
650 return;
651 }
652
653 if (module_exists('path')) {
654 if (isset($entity->path)) {
655 $path = $entity->path;
656 $path['alias'] = trim($path['alias']);
657 // Delete old alias if user erased it.
658 if (!empty($path['pid']) && empty($path['alias'])) {
659 path_delete($path['pid']);
660 }
661 // Only save a non-empty alias.
662 if (!empty($path['alias'])) {
663 // Ensure fields for programmatic executions.
664 $path['source'] = 'section/' . $entity->name;
665 $path['language'] = LANGUAGE_NONE;
666 path_save($path);
667 }
668 }
669 }
670
671 if (module_exists('pathauto')) {
672 section_update_alias($entity, 'update');
673 }
674 }
675
676 /**
677 *
678 */
679 function section_entity_delete($entity, $type) {
680 if ($type !== 'section') {
681 return;
682 }
683
684 if (module_exists('path')) {
685 path_delete(array('source' => 'section/' . $entity->name));
686 }
687
688 if (module_exists('pathauto')) {
689 pathauto_entity_path_delete_all('section', $entity, "section/{$entity->name}");
690 }
691 }
692
693 /**
694 * Menu callback; Retrieve a JSON object containing autocomplete suggestions for
695 * existing sections.
696 */
697 function section_autocomplete($string = '') {
698 $matches = array();
699 if ($string) {
700 $result = db_select('section', 's')
701 ->fields('s', array('name', 'label'))
702 ->condition('name', '%' . db_like($string) . '%', 'LIKE')
703 ->range(0, 10)
704 ->execute();
705 foreach ($result as $section) {
706 $matches[$section->name] = check_plain($section->label);
707 }
708 }
709
710 drupal_json_output($matches);
711 }
712
713 /**
714 * Implements hook_path_alias_types().
715 */
716 function section_path_alias_types() {
717 $objects['section/'] = t('Section');
718
719 return $objects;
720 }
721
722 /**
723 * Implements hook_pathauto().
724 */
725 function section_pathauto($op) {
726 switch ($op) {
727 case 'settings':
728 $settings = array();
729 $settings['module'] = 'section';
730 $settings['token_type'] = 'section';
731 $settings['groupheader'] = t('Section paths');
732 $settings['patterndescr'] = t('Default path pattern (applies to all section types with blank patterns below)');
733 $settings['patterndefault'] = 'section/[section:name]';
734 $settings['batch_update_callback'] = 'section_pathauto_bulk_update_batch_process';
735 $settings['batch_file'] = drupal_get_path('module', 'section') . '/section.pathauto.inc';
736
737 $bundles = section_bundles();
738 foreach ($bundles as $bundle) {
739 $settings['patternitems'][$bundle->name] = t('Pattern for all @section_type paths', array('@section_type' => $bundle->label));
740 }
741 return (object) $settings;
742 default:
743 break;
744 }
745 }
746
747 /**
748 * Update the URL aliases for an individual section.
749 */
750 function section_update_alias($section, $op, array $options = array()) {
751 // Skip processing if the user has disabled pathauto for the node.
752 if (isset($section->path['pathauto']) && empty($section->path['pathauto'])) {
753 return;
754 }
755
756 // Skip processing if the section has no pattern.
757 if (!pathauto_pattern_load_by_entity('section', $section->bundle)) {
758 return;
759 }
760
761 module_load_include('inc', 'pathauto');
762 $uri = entity_uri('section', $section);
763 pathauto_create_alias('section', $op, $uri['path'], array('section' => $section), $section->bundle);
764 }
765
766 /**
767 * Update the URL aliases for multiple sections.
768 */
769 function section_update_alias_multiple(array $sids, $op, array $options = array()) {
770 $options += array('message' => FALSE);
771
772 $sections = section_load_multiple($sids);
773 foreach ($sections as $section) {
774 section_update_alias($section, $op, $options);
775 }
776
777 if (!empty($options['message'])) {
778 drupal_set_message(format_plural(count($sids), 'Updated URL alias for 1 section.', 'Updated URL aliases for @count sections.'));
779 }
780 }
781
782 /**
783 *
784 */
785 function section_object_prepare(&$section) {
786 $bundle = section_bundle_load($section->bundle);
787 $bundle_settings = unserialize($bundle->settings);
788 $settings = $bundle_settings['section_options'];
789
790 if (!isset($section->sid) || isset($section->is_new)) {
791
792 }
793 else {
794 $node->log = NULL;
795 }
796
797 // Always use the default revision setting.
798 $section->revision = in_array('revision', $settings);
799 }