26417538cc2498b3f1c21eac478cb89ab7145086
[project/better_formats.git] / better_formats.module
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * Enhances Drupal's core input format settings.
7 *
8 * Allows setting of defaults per role and content type,
9 * controls format display options, works with CCK fields.
10 */
11
12 /**
13 * Implementation of hook_help().
14 */
15 function better_formats_help($path, $arg) {
16 switch ($path) {
17 case 'admin/help/better_formats':
18 $output = '<p>' . t('See the module README.txt file in the better_formats module directory for help.') . '</p>';
19 break;
20
21 case 'admin/settings/filters/defaults':
22 $output = '<p>' . t('Set the global default formats per role for NEW nodes and comments. These settings will be applied to all nodes and comments in the site unless overriden by specific content type defaults.') . '</p>';
23 $output .= '<p>' . t('Arrange the roles to provide weight that will determine what format is selected when a user has more than one role. Remember, that all logged in users are automatically given the authenticated user role in addition to their other assigned roles. For example, if you have an admin role place it at the top and generally you would want your anonymous user role at the bottom.') . '</p>';
24 break;
25
26 default:
27 $output = '';
28 }
29 return $output;
30 }
31
32 /**
33 * Implementation of hook_perm().
34 */
35 function better_formats_perm() {
36 return array(
37 'show format selection',
38 'show format tips',
39 'show more format tips link',
40 'collapse format fieldset by default',
41 'collapsible format selection',
42 );
43 }
44
45 /**
46 * Implementation of hook_menu().
47 */
48 function better_formats_menu() {
49 $items = array();
50
51 $items['admin/settings/filters/settings'] = array(
52 'title' => 'Settings',
53 'description' => 'Manage input formats',
54 'page callback' => 'drupal_get_form',
55 'page arguments' => array('better_formats_settings_admin_form'),
56 'access arguments' => array('administer filters'),
57 'type' => MENU_LOCAL_TASK,
58 'weight' => 3,
59 'file' => 'better_formats_settings.admin.inc',
60 );
61 $items['admin/settings/filters/defaults'] = array(
62 'title' => 'Defaults',
63 'description' => 'Manage input formats',
64 'page callback' => 'drupal_get_form',
65 'page arguments' => array('better_formats_defaults_admin_form'),
66 'access arguments' => array('administer filters'),
67 'type' => MENU_LOCAL_TASK,
68 'weight' => 2,
69 'file' => 'better_formats_defaults.admin.inc',
70 );
71
72 return $items;
73 }
74
75 /**
76 * Implementation of hook_theme().
77 */
78 function better_formats_theme() {
79 return array(
80 'better_formats_defaults_admin_form' => array(
81 'template' => 'better-formats-defaults-admin-form',
82 'file' => 'better_formats_defaults.admin.inc',
83 'arguments' => array('form' => NULL),
84 ),
85 'better_formats_filter_tips_more_info' => array(
86 'arguments' => array(),
87 ),
88 );
89 }
90
91 /**
92 * Implementation of hook_form_alter().
93 */
94 function better_formats_form_alter(&$form, $form_state, $form_id) {
95 // alter new node and comment forms
96 // using $form['#id'] instead of $form_id because $form_id is in the form of
97 // 'contentType_node_form' which varies with the content type
98 // while $form['#id'] is always 'node-form'
99 switch ($form['#id']) {
100 case 'comment-form':
101 better_formats_set_comment_format($form);
102 break;
103
104 case 'node-form':
105 better_formats_set_node_format($form);
106 break;
107 }
108
109 // alter role add or delete, and node type
110 switch ($form_id) {
111 case 'node_type_form':
112 if (variable_get('better_formats_per_node_type', FALSE)) {
113 better_formats_node_type_form($form, $form_state);
114 }
115 break;
116
117 case 'user_admin_new_role':
118 if ( ! in_array('better_formats_new_role', $form['#submit'])) {
119 $form['#submit'][] = 'better_formats_new_role';
120 }
121 break;
122
123 case 'user_admin_role':
124 if (isset($form_state['post']['op']) && $form_state['post']['op'] == 'Delete role') {
125 $form['#submit'][] = 'better_formats_delete_role';
126 }
127 break;
128 }
129 }
130
131 /**
132 * FAPI form to add to the content type edit form.
133 *
134 * @see better_formats_node_type_form_validate()
135 * @see better_formats_node_type_form_submit()
136 */
137 function better_formats_node_type_form(&$form, $form_state) {
138 // add js to enhance form and fix a bug
139 drupal_add_js(drupal_get_path('module', 'better_formats') . '/better_formats_node_type_form.js');
140
141 $node_type = $form['#node_type']->type;
142
143 // build array of all formats for allowed checkboxes
144 $formats = filter_formats();
145 foreach ($formats as $format) {
146 $format_boxes[$format->format] = $format->name;
147 }
148
149 $key = 'better_formats';
150 $form[$key] = array(
151 '#type' => 'fieldset',
152 '#title' => t('Input format settings'),
153 '#access' => user_access('administer filters'),
154 '#collapsible' => TRUE,
155 // setting collapsed to false because the wieght will not be hidden otherwise
156 // the fieldset will be collapsed via JS if enabled
157 '#collapsed' => FALSE,
158 '#attributes' => array('class' => 'input-format-settings'),
159 );
160 $allowed_key = $key . '_allowed';
161 $form[$key][$allowed_key] = array(
162 '#type' => 'checkboxes',
163 '#title' => t('Allowed formats'),
164 '#default_value' => variable_get($allowed_key . '_' . $node_type, array()),
165 '#options' => $format_boxes,
166 '#description' => t('Limit the formats users have to choose from even if they have permission to use that format. This will NOT allow a user to use a format they do not have access rights to use. It will only hide additional formats they do have access rights to. If no boxes are checked, all formats that the user has permission to use will be allowed.'),
167 '#attributes' => array('class' => 'bf-allowed-formats'),
168 );
169
170 $dform = array(
171 '#tree' => TRUE,
172 '#theme' => 'better_formats_defaults_admin_form',
173 );
174 $nform = better_formats_get_role_default_fields('node', $node_type);
175 $cform = better_formats_get_role_default_fields('comment', $node_type);
176
177 $form[$key]['better_formats_defaults'] = array_merge($dform, $nform, $cform);
178
179 // Attach our validate and submit handlers.
180 // Prepending to the submit array because core will auto save the values in the
181 // variable table if the values are not removed before hand.
182 $form['#validate'][] = 'better_formats_node_type_form_validate';
183 $form['#submit'][] = 'better_formats_node_type_form_submit';
184 }
185
186 /**
187 * Handles validatation of the addition to the content type edit form.
188 *
189 * @see better_formats_node_type_form()
190 * @see better_formats_node_type_form_submit()
191 */
192 function better_formats_node_type_form_validate($form, &$form_state) {
193 module_load_include('admin.inc', 'better_formats', 'better_formats_defaults');
194 better_formats_defaults_admin_form_validate($form, $form_state);
195 }
196
197 /**
198 * Handles submition of the addition to the content type edit form.
199 *
200 * @see better_formats_node_type_form()
201 * @see better_formats_node_type_form_validate()
202 */
203 function better_formats_node_type_form_submit($form, &$form_state) {
204 $node_type = trim($form_state['values']['type']);
205
206 // remove current db entries
207 $sql = "DELETE FROM {better_formats_defaults}
208 WHERE type='comment/%s' OR type='node/%s'";
209 db_query($sql, $node_type, $node_type);
210
211 // insert defualt values into DB
212 $sql = "INSERT INTO {better_formats_defaults}
213 VALUES (%d, '%s', %d, %d, %d)";
214 foreach ($form_state['values']['better_formats_defaults'] as $key => $values) {
215 if (strpos($key, 'node-') === 0 || strpos($key, 'comment-') === 0) {
216 list($type, $rid) = explode('-', $key);
217 db_query($sql, $rid, $type . '/' . $node_type, $values['format'], 2, $values['weight']);
218 }
219 }
220
221 // node module automatically stores all settings in variable table
222 // BF uses default settings from its own table
223 // so delete the unneeded variable
224 variable_del('better_formats_defaults_' . $node_type);
225 }
226
227 /**
228 * Creates base format default entry for a newly created role.
229 *
230 * @see better_formats_form_alter()
231 */
232 function better_formats_new_role($form, &$form_state) {
233 // get the rid for the role just created
234 $sql = "SELECT rid
235 FROM {role}
236 ORDER BY rid DESC";
237 $rid = db_fetch_object(db_query_range($sql, 0, 1))->rid;
238
239 // create stubs in per role table
240 $sql = "INSERT INTO {better_formats_defaults}
241 VALUES (%d, '%s', %d, %d, %d)";
242 db_query($sql, $rid, 'node', 0, 1, 25);
243 db_query($sql, $rid, 'comment', 0, 1, 25);
244 }
245
246 /**
247 * Deletes role format default entries for roles being deleted.
248 *
249 * @see better_formats_form_alter()
250 */
251 function better_formats_delete_role($form, &$form_state) {
252 // delete role from format manager table
253 $sql = "DELETE FROM {better_formats_defaults}
254 WHERE rid = %d";
255 db_query($sql, $form['rid']['#value']);
256 }
257
258 /**
259 * Implementation of hook_node_type().
260 */
261 function better_formats_node_type($op, $info) {
262 if ($op === 'delete') {
263 // delete per node type settings on node type delete
264 $sql = "DELETE FROM {better_formats_defaults}
265 WHERE type IN ('node/%s', 'comment/%s')";
266 db_query($sql, $info->type, $info->type);
267
268 // delete node type variables
269 variable_del('better_formats_allowed_' . $info->type);
270 }
271 }
272
273 /**
274 * Implementation of hook_elements().
275 *
276 * Adds a process function to CCK's textarea FAPI element
277 */
278 function better_formats_elements() {
279 return array(
280 'text_textarea' => array(
281 '#process' => array('better_formats_textarea_process'),
282 ),
283 );
284 }
285
286 /**
287 * Processes a CCK textarea element.
288 *
289 * Resets the textareas filter area with bettter_formats default.
290 * This function is used to affect CCK textareas not core fields.
291 *
292 * @see text_textarea_process()
293 */
294 function better_formats_textarea_process($element, $edit, $form_state, $form) {
295 $debug = variable_get('better_formats_debug', FALSE);
296 $field = $form['#field_info'][$element['#field_name']];
297
298 if ($debug) {
299 drupal_set_message('CCK textarea field: <br /><pre>' . print_r($field, TRUE) . '</pre>');
300 }
301
302 if ( ! empty($field['text_processing'])) {
303 // get core default for new or selected format for existing
304 $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
305 $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
306 $parents = array_merge($element['#parents'] , array($filter_key));
307 $default = better_formats_get_default_format('node', $form['type']['#value']);
308
309 // overwrite format default if new node
310 if ( ! isset($form_state['values']['nid'])) {
311 $format = $default;
312 }
313
314 // set default format for cck textarea
315 $element['#value'][$filter_key] = $format;
316 // set filter selection form
317 $element[$filter_key] = better_formats_filter_form($format, $default, $form['type']['#value'], 1, $parents);
318 }
319
320 return $element;
321 }
322
323 /**
324 * Processes formats for core node body fields.
325 *
326 * @see better_formats_form_alter()
327 */
328 function better_formats_set_node_format(&$form) {
329 // set core body field
330 if (isset($form['body_field'])) {
331 // get default for new entries
332 $default = better_formats_get_default_format('node', $form['type']['#value']);
333
334 if (empty($form['nid']['#value'])) {
335 // set format to default for new entries
336 $format = $default;
337 }
338 else {
339 // get existing format for core body field
340 $format = better_formats_get_current_format($form['body_field']['format']);
341 }
342
343 // overwrite the filter form with our own
344 $form['body_field']['format'] = better_formats_filter_form($format, $default, $form['type']['#value']);
345 }
346 }
347
348 /**
349 * Processes formats for core node comment form.
350 *
351 * @see better_formats_form_alter()
352 */
353 function better_formats_set_comment_format(&$form) {
354 if (isset($form['comment_filter']['format'])) {
355 $node = node_load($form['nid']['#value']);
356
357 // get bf default format
358 $default = better_formats_get_default_format('comment', $node->type);
359
360 if (empty($form['cid']['#value'])) {
361 // set format to default for new entries
362 $format = $default;
363 }
364 else {
365 // get existing format for comment
366 $format = better_formats_get_current_format($form['comment_filter']['format']);
367 }
368 // overwrite the filter form with our own
369 $form['comment_filter']['format'] = better_formats_filter_form($format, $default, $node->type);
370 }
371 }
372
373 /**
374 * Returns the format for an existing node or comment.
375 *
376 * @param array $form
377 * FAPI form array
378 * @return int
379 * Format ID
380 *
381 * @see better_formats_set_node_format()
382 * @see better_formats_set_comment_format()
383 */
384 function better_formats_get_current_format($form) {
385 // default format to site default in case of error
386 $format = FILTER_FORMAT_DEFAULT;
387 foreach (element_children($form) as $key) {
388 $element = $form[$key];
389 if ($element['#type'] === 'radio' && isset($element['#default_value'])) {
390 $format = $element['#default_value'];
391 break;
392 }
393 if ($element['#type'] === 'value' && isset($element['#value'])) {
394 $format = $element['#value'];
395 break;
396 }
397 }
398 return $format;
399 }
400
401 /**
402 * Returns the default format for an new node or comment.
403 *
404 * @param string $mode
405 * 'node' or 'comment'. Describes the top level type of default.
406 * @return int
407 * Format ID
408 *
409 * @see better_formats_set_node_format()
410 * @see better_formats_set_comment_format()
411 * @see better_formats_textarea_process()
412 */
413 function better_formats_get_default_format($mode, $node_type = '') {
414 static $format;
415
416 // default our type to the mode (node or comment)
417 $type = $mode;
418
419 // check if per node type is enabled and set type accordingly
420 $per_node_type = variable_get('better_formats_per_node_type', FALSE);
421 if ($per_node_type && $node_type) {
422 $type = $mode . '/' . $node_type;
423 }
424
425 // only pull from the DB if we have not already checked for this specific type
426 if ( ! isset($format[$type])) {
427 // get users roles
428 global $user;
429
430 $types = $type;
431 $format = array();
432 $roles = implode(',', array_keys($user->roles));
433
434 if ($per_node_type && $node_type) {
435 $types .= "','" . $mode;
436 }
437
438 // get users lowest weight role default
439 $sql = "SELECT format
440 FROM {better_formats_defaults}
441 WHERE rid IN (%s) AND type IN ('$types')
442 ORDER BY type_weight DESC, weight ASC";
443 $value = db_fetch_object(db_query_range($sql, $roles, 0, 1))->format;
444 $format[$type] = filter_resolve_format($value);
445 }
446
447 return $format[$type];
448 }
449
450
451 /**
452 * Better Formats version of filter_form().
453 *
454 * Copied from filter.module with slight modification to
455 * handle options for hiding filter selection and/or tips.
456 * The $node_type param was added to the signature to
457 * enable condition by content type.
458 *
459 * @see filter_form()
460 */
461 function better_formats_filter_form($value = FILTER_FORMAT_DEFAULT, $default_format, $node_type = '', $weight = 1, $parents = array('format')) {
462 static $debug1;
463 $debug = variable_get('better_formats_debug', FALSE);
464 $value = filter_resolve_format($value);
465 $formats = filter_formats();
466 $show_selection = user_access('show format selection');
467 $show_tips = user_access('show format tips');
468 $show_tips_link = user_access('show more format tips link');
469 $per_node_type = variable_get('better_formats_per_node_type', FALSE);
470 $allowed_formats = variable_get('better_formats_allowed_' . $node_type, FALSE);
471
472 if ($debug && ! $debug1) {
473 drupal_set_message('Default format value: ' . $value);
474 drupal_set_message('Node type: ' . $node_type);
475 drupal_set_message('Show format selection: ' . $show_selection);
476 drupal_set_message('Show format tips: ' . $show_tips);
477 drupal_set_message('Show more format tips link: ' . $show_tips_link);
478 drupal_set_message('Control per node type: ' . $per_node_type);
479 drupal_set_message('Node type allowed formats: <br /><pre>' . print_r($allowed_formats, TRUE) . '</pre>');
480 drupal_set_message('Core allowed formats: <br /><pre>' . print_r($formats, TRUE) . '</pre>');
481 }
482
483 // check if there are node type restrictions on allowed formats
484 // if there are no retrictions set we use the site globals as default
485 if ($per_node_type && $allowed_formats) {
486 foreach ($formats as $key => $format) {
487 if ( ! in_array($format->format, $allowed_formats)) {
488 unset($formats[$key]);
489 }
490 }
491 }
492
493 if ($debug && ! $debug1) {
494 drupal_set_message('BF allowed formats: <br /><pre>' . print_r($formats, TRUE) . '</pre>');
495 }
496
497 // extra check to ensure default format is available to the user
498 // else we fall back to the site default format
499 $default = isset($formats[$value]) ? $formats[$value]->format : filter_resolve_format(FILTER_FORMAT_DEFAULT);
500
501 if (isset($formats[$value])) {
502 // use existing or bf default value if available
503 $default = $value;
504 }
505 else if (isset($formats[$default_format])) {
506 // use currently set bf default as a fallback
507 $default = $default_format;
508 }
509 else {
510 // use core site default as a fallback if the previous two are not available
511 $default = filter_resolve_format(FILTER_FORMAT_DEFAULT);
512 }
513
514 if (count($formats) > 1 && $show_selection) {
515 $collapsed = user_access('collapse format fieldset by default');
516 $collapsible = user_access('collapsible format selection');
517 $fieldset_title = variable_get('better_formats_fieldset_title', 'Input format');
518
519 if ($debug && ! $debug1) {
520 drupal_set_message('Collapsible format selection: ' . $collapsible);
521 drupal_set_message('Collape format selection by default: ' . $collapsed);
522 }
523
524 $form = array(
525 '#type' => 'fieldset',
526 '#title' => t('@title', array('@title' => $fieldset_title ? $fieldset_title : 'Input format')),
527 '#collapsible' => $collapsible,
528 '#collapsed' => $collapsed,
529 '#weight' => $weight,
530 '#element_validate' => array('filter_form_validate'),
531 );
532
533 // Multiple formats available: display radio buttons with tips.
534 foreach ($formats as $format) {
535 // Generate the parents as the autogenerator does, so we will have a
536 // unique id for each radio button.
537 $parents_for_id = array_merge($parents, array($format->format));
538 $form[$format->format] = array(
539 '#type' => 'radio',
540 '#title' => $format->name,
541 '#default_value' => $default,
542 '#return_value' => $format->format,
543 '#parents' => $parents,
544 '#id' => form_clean_id('edit-'. implode('-', $parents_for_id)),
545 );
546 if ($show_tips) {
547 $form[$format->format]['#description'] = theme('filter_tips', _filter_tips($format->format, FALSE));
548 }
549 else {
550 // this is to get around an issue with Wysiwyg API popping the last element
551 // should not be needed in D7
552 // see http://drupal.org/node/344169
553 $form[$format->format]['#description'] = '';
554 }
555 }
556
557 if ($show_tips_link) {
558 $extra = theme('better_formats_filter_tips_more_info');
559 $form[] = array('#value' => $extra);
560 }
561 else {
562 // this is to get around an issue with Wysiwyg API popping the last element
563 // should not be needed in D7
564 // see http://drupal.org/node/344169
565 $form[] = array('#value' => '');
566 }
567 }
568 else {
569 // Only one format available or hiding the form: use a hidden form item.
570 $format = $formats[$default];
571 $form[$format->format] = array(
572 '#type' => 'value',
573 '#value' => $format->format,
574 '#parents' => $parents
575 );
576
577 if ($show_tips) {
578 $tips = _filter_tips($format->format, FALSE);
579 $form['format']['guidelines'] = array(
580 '#title' => t('Formatting guidelines'),
581 '#value' => theme('filter_tips', $tips, FALSE),
582 );
583 }
584 else {
585 // this is to get around an issue with Wysiwyg API looking for guidelines
586 // should not be needed in D7
587 // see http://drupal.org/node/344169
588 $form['format']['guidelines'] = array(
589 '#title' => t('Formatting guidelines'),
590 '#value' => '',
591 );
592 }
593
594 // only show long tips link if there are guidelines to the format
595 if ($show_tips_link) {
596 $extra = theme('better_formats_filter_tips_more_info');
597 $form[] = array('#value' => $extra);
598 } else {
599 // see http://drupal.org/node/344169
600 $form[] = array('#value' => '');
601 }
602 }
603
604 // set debug1 to true so that some debug info is only printed once
605 $debug1 = TRUE;
606
607 return $form;
608 }
609
610
611 function theme_better_formats_filter_tips_more_info() {
612 $text = variable_get('better_formats_long_tips_link_text', '');
613 $text = $text ? $text : t('More information about formatting options');
614 return '<p>' . l($text, 'filter/tips') . '</p>';
615 }
616
617
618 /**
619 * Retrieves the formats available to users by role.
620 *
621 * Gets all formats then creates an array keyed by role IDs
622 * that lists the formats available to that role. This is determined
623 * by Drupal core's format permissions set at
624 * admin/settings/filters/[filter_id].
625 *
626 * @param string $default_title
627 * Allows configuration of the label of the default seletion
628 * @return array
629 * Multi-dim array with role IDs for keys and list of allowed formats.
630 *
631 * @see better_formats_get_role_default_fields()
632 */
633 function better_formats_get_formats_per_role($default_title = 'Site') {
634 $formats = filter_formats();
635 $roles = user_roles();
636
637 // get roles that have administer filters permission
638 $sql = "SELECT rid
639 FROM {permission}
640 WHERE perm LIKE '%administer filters%'
641 ORDER BY rid";
642 $result = db_query($sql);
643 $admin_roles = array();
644 while ($row = db_fetch_object($result)) {
645 $admin_roles[] = $row->rid;
646 }
647
648 $site_default_format = filter_resolve_format(FILTER_FORMAT_DEFAULT);
649
650 foreach ($formats as $format) {
651 $roles_allowed = $format->roles ? explode(',', trim($format->roles, ',')) : array();
652 foreach ($roles as $rid => $role) {
653 $format_options[$rid][0] = $default_title . ' default';
654 if ($format->format == $site_default_format ||
655 in_array($rid, $admin_roles) ||
656 in_array($rid, $roles_allowed)
657 ) {
658 $format_options[$rid][$format->format] = $format->name;
659 }
660 }
661 }
662
663 return $format_options;
664 }
665
666 /**
667 * Builds FAPI form elements for the default format selection.
668 *
669 * @param string $mode
670 * 'node' or 'comment'. Top most level type for requested default.
671 * @param string $node_type
672 * Type of node this request is for.
673 * @return array
674 * FAPI array for the default select field.
675 */
676 function better_formats_get_role_default_fields($mode, $node_type = '') {
677 $form = array();
678 $format_options = better_formats_get_formats_per_role();
679 $type = $types = $mode;
680 $per_node_type = variable_get('better_formats_per_node_type', FALSE);
681
682 if ($per_node_type && $node_type) {
683 $type = $mode . '/' . $node_type;
684 $types = $type . "','" . $mode;
685 }
686
687 // get data from db
688 $sql = "SELECT bf.*, role.name
689 FROM {better_formats_defaults} AS bf
690 INNER JOIN {role} AS role
691 ON bf.rid = role.rid
692 WHERE bf.type IN ('$types')
693 ORDER BY bf.type_weight DESC, bf.weight, role.rid";
694 $result = db_query($sql);
695
696 $roles_set = array();
697
698 while ($role = db_fetch_object($result)) {
699 if (in_array($role->rid, $roles_set)) {
700 continue;
701 }
702
703 $roles_set[] = $role->rid;
704 $key = $mode . '-' . $role->rid;
705
706 $form[$key]['role'] = array(
707 '#value' => $role->name,
708 );
709 $form[$key]['format'] = array(
710 '#type' => 'select',
711 '#title' => t('Format'),
712 '#options' => $format_options[$role->rid],
713 '#default_value' => $role->format,
714 '#attributes' => array('class' => 'bf-default-formats'),
715 );
716 $form[$key]['weight'] = array(
717 '#type' => 'weight',
718 '#delta' => 25,
719 '#default_value' => $role->weight,
720 );
721 }
722
723 return $form;
724 }