/[drupal]/contributions/modules/cck_taxonomy_ssu/cck_taxonomy_ssu.module
ViewVC logotype

Contents of /contributions/modules/cck_taxonomy_ssu/cck_taxonomy_ssu.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.8 - (show annotations) (download) (as text)
Wed Feb 27 09:05:26 2008 UTC (20 months, 4 weeks ago) by rconstantine
Branch: MAIN
CVS Tags: HEAD
Changes since 1.7: +322 -85 lines
File MIME type: text/x-php
#183478 alphabetizing now correct
#184161 setting field as 'required' now works properly
#215079 not sure if it was broken in the first place, but 'ratings' can be properly changed and checkboxes can be unchecked as expected
#212624 hopefully fixed a views exposed filter thing
#216821 again, not sure it was broken, but terms are properly shown/added the first time the node is saved
#200786 new feature: restrict creation of top level branches
#205093 new feature: restrict creation of branches
#221028 i think the last two items cover this one as well
#215170 changed how terms were saved to use native taxonomy functions which makes sure pathauto gets a hold of them properly
#177988 new feature: can now add multiple terms at once

still broken: single select of terms (radio button); see http://drupal.org/node/227306
1 <?php
2 /**
3 * This is the cck_taxonomy_ssu module for use with CCK.
4 *
5 * <p>This file contains information on the cck_taxonomy_ssu module. The module adds to
6 * the field types available for inclusion in a content type definition. This field
7 * is made up of a heirarchical, collapsable list of checkboxes for each level of depth
8 * of the selected vocabulary and is used in place of a multi-select box.</p>
9 * <p></p>
10 *
11 * @version $Id$;
12 * @package CCK_Taxonomy_SSU
13 * @category NeighborForge
14 * @author Ryan Constantine
15 * @filesource
16 * @license http://www.gnu.org/licenses/gpl.txt GNU_GENERAL_PUBLIC_LICENSE
17 * @link none yet
18 */
19
20 /**
21 * This is the length of the cck field name - "cck_taxonomy_ssu_"
22 */
23 define('CCK_FIELD_NAME_LENGTH', 17);
24
25 //-----------------------------------------CCK Field Hooks-------------------------------------
26 //-----------------------------------------CCK Field Hooks-------------------------------------
27 //-----------------------------------------CCK Field Hooks-------------------------------------
28
29 /**
30 * Implementation of hook_field_info().
31 *
32 * @return
33 * An array keyed by field type name. Each element of the array is an associative
34 * array with these keys and values:
35 * - "label": The human-readable label for the field type.
36 */
37 function cck_taxonomy_ssu_field_info() {
38 return array(
39 'cck_taxonomy_ssu' => array('label' => 'Taxonomy vocabulary (super select ultra)'),
40 );
41 } // function cck_taxonomy_ssu_field_info()
42
43 /**
44 * Implementation of hook_field_settings().
45 *
46 * @param $op
47 * The operation to be performed.
48 * @param $field
49 * The field on which the operation is to be performed.
50 * @return
51 * This varies depending on the operation.
52 * - "form": an array of form elements to add to
53 * the settings page.
54 * - "validate": no return value. Use form_set_error().
55 * - "save": an array of names of form elements to
56 * be saved in the database.
57 * - "database columns": an array keyed by column name, with arrays of column
58 * information as values.
59 * - "filters": an array whose values are 'filters'
60 * definitions as expected by views.module (see Views Documentation).
61 * - "callbacks": an array describing the field's behaviour regarding hook_field
62 * operations. The array is keyed by hook_field operations ('view', 'validate'...)
63 * and has the following possible values :
64 * CONTENT_CALLBACK_NONE : do nothing for this operation
65 * CONTENT_CALLBACK_CUSTOM : use the behaviour in hook_field(operation)
66 * CONTENT_CALLBACK_DEFAULT : use content.module's default bahaviour
67 * Note : currently only the 'view' operation implements this feature.
68 * All other field operation implemented by the module _will_ be executed
69 * no matter what.
70 */
71 function cck_taxonomy_ssu_field_settings($op, &$field) {
72 switch ($op) {
73 case 'form':
74 $form = array();
75 $form['top_level_freetag'] = array(
76 '#type' => 'checkbox',
77 '#title' => t('Disallow freetagging at the top level of the vocabulary'),
78 '#default_value' => isset($field['top_level_freetag']) ? $field['top_level_freetag'] : 0,
79 '#return_value' => 1,
80 '#description' => t('This prevents users from adding branches to your vocabulary\'s top level. If freetagging is enabled and you don\'t provide branches to freetag in, users will not be able to freetag if this is enabled.'),
81 );
82 $form['no_freetag_branching'] = array(
83 '#type' => 'checkbox',
84 '#title' => t('Disallow freetagging on leaf terms'),
85 '#default_value' => isset($field['no_freetag_branching']) ? $field['no_freetag_branching'] : 0,
86 '#return_value' => 1,
87 '#description' => t('This prevents users from adding branches to leaf terms. You will have to setup any and all branches manually via some other module as creation of branches will not be possible with this enabled.'),
88 );
89 $form['parents'] = array(
90 '#type' => 'checkbox',
91 '#title' => t('Display parent terms as selectable form items'),
92 '#default_value' => isset($field['parents']) ? $field['parents'] : 0,
93 '#return_value' => 1,
94 '#description' => t('Leaving this disabled forces users to select dangling child terms. Useful for grouping terms with descriptive parent terms that are not themselves needed for display.'),
95 );
96 $form['tags'] = array(
97 '#type' => 'checkbox',
98 '#title' => t('Add used terms as node tags?'),
99 '#default_value' => isset($field['tags']) ? $field['tags'] : 0,
100 '#return_value' => 1,
101 '#description' => t('Normally, this module does not include selected vocabulary items in the node\'s taxonomy field, and therefore does not save anything to the term_node table.
102 This prevents nodes from showing up in taxonomy term searches and related operations. Checking this box will make that association and allow tags for nodes.
103 This only affects this content type, not all which use this vocabulary.'),
104 );
105 $form['ratings'] = array(
106 '#type' => 'fieldset',
107 '#title' => t('Term ratings'),
108 '#description' => t('This selector will be filled with the text you provide below. It can be used for anything.
109 For example, if the taxonomy are skills, this box could denote skill level. If the taxonomy are movies, the box could contain ratings - G, PG, PG-13, R - or *, **, ***, ****, *****'),
110 '#collapsible' => TRUE,
111 '#collapsed' => TRUE,
112 );
113 $form['ratings']['rate_yesno'] = array(
114 '#type' => 'checkbox',
115 '#title' => t('Show ratings selector'),
116 '#default_value' => isset($field['rate_yesno']) ? $field['rate_yesno'] : '',
117 );
118 $form['ratings']['choice_title'] = array(
119 '#type' => 'textfield',
120 '#title' => t('Choice title'),
121 '#description' => t('Name the set of choices below.'),
122 '#maxlength' => 30,
123 '#size' => 25,
124 '#default_value' => isset($field['choice_title']) ? $field['choice_title'] : '',
125 );
126 $form['ratings']['choices'] = array(
127 '#type' => 'textarea',
128 '#title' => t('Choices'),
129 '#description' => t('List the choices that should be presented to the user, one per line.'),
130 '#default_value' => isset($field['choices']) ? $field['choices'] : '',
131 '#cols' => 40,
132 '#rows' => 6,
133 '#resizable' => FALSE,
134 );
135 return $form;
136
137 /*case 'validate':
138 break;*/
139
140 case 'save':
141 return array(
142 'top_level_freetag',
143 'no_freetag_branching',
144 'parents',
145 'tags',
146 'rate_yesno',
147 'choices',
148 'choice_title'
149 );
150
151 case 'database columns':
152 $columns = array(
153 'tid' => array('type' => 'int', 'length' => 10, 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
154 'choice' => array('type' => 'varchar', 'length' => 40, 'not null' => FALSE, 'sortable' => TRUE),
155 );
156 return $columns;
157
158 /*case 'filters':
159 return array(
160 'default' => array(
161 'operator' => 'views_handler_operator_like',//instead of like, I should do ==
162 'handler' => 'views_handler_filter_like',
163 ),
164 );
165
166 case 'callbacks'://pairs up with cck_taxonomy_ssu_field::view
167 return array(
168 'view' => CONTENT_CALLBACK_CUSTOM,
169 );*/
170 }
171 } // function cck_taxonomy_ssu_field_settings()
172
173
174 /**
175 * Implementation of hook_field().
176 *
177 * <p>-Validate the user's input.</p>
178 * <p>-Alternatively, present the data for viewing.</p>
179 * @param $op
180 * What kind of action is being performed.
181 * @param &$node
182 * The node the action is being performed on.
183 * @param $field
184 * The field the action is being performed on.
185 * @param &$items
186 * The contents of the field in this node. Changes to this variable will
187 * be saved back to the node object.
188 * @return
189 * This varies depending on the operation.
190 * - The "load" operation should return an object containing extra values
191 * to be merged into the node object.
192 * - The "view" operation should return a string containing an HTML
193 * representation of the field data.
194 * - The "insert", "update", "delete", "validate", and "submit" operations
195 * have no return value.
196 */
197 function cck_taxonomy_ssu_field($op, &$node, $field, &$items, $teaser, $page) {
198 switch ($op) {
199 //case 'validate':
200 case 'submit':
201 //case 'insert':
202 //case 'update':
203 //this makes the terms into regular node tags
204 if ($field['tags'] == 1) {
205 $vid = cck_taxonomy_ssu_get_vid($field);
206 foreach ($items as $delta => $item) {
207 if (is_numeric($delta) && isset($items[$delta]['tid']) && $items[$delta]['tid'] == $delta) {
208 $node->taxonomy[$vid][$delta] = $delta;
209 }
210 }
211 }
212 break;
213 case 'view':
214 foreach ($items as $delta => $item) {
215 $items[$delta]['view'] = content_format($field, $item, 'default', $node);
216 }
217 return theme('field', $node, $field, $items, $teaser, $page);
218 }
219 } //function cck_taxonomy_ssu_field()
220
221
222 /**
223 * Implementation of hook_field_formatter_info().
224 */
225 function cck_taxonomy_ssu_field_formatter_info() {
226 return array(
227 'default' => array(
228 'label' => 'Default',
229 'field types' => array('cck_taxonomy_ssu'),
230 ),
231 );
232 } // function cck_taxonomy_ssu_field_formatter_info()
233
234
235 /**
236 * Implementation of hook_field_formatter().
237 *
238 * <p>Here we format the data for display and make sure it is plain text. It should be as
239 * elsewhere it was validated as alphanumeric characters only.</p>
240 * <p>The $node argument is necessary so that filter access can be checked on
241 * node preview.</p>
242 * @param $field
243 * The field the action is being performed on.
244 * @param $item
245 * An array, keyed by column, of the data stored for this item in this field.
246 * @param $formatter
247 * The name of the formatter being used to display the field. In our case, we name
248 * it directly, rather than send it through content_format() and therefore we don't
249 * use hook_field_formatter_info either.
250 * @param $node
251 * The node object, for context. Will be NULL in some cases.
252 * Warning : when displaying field retrieved by Views, $node will not
253 * be a "full-fledged" node object, but an object containg the data returned
254 * by the Views query (at least nid, vid, changed)
255 * @return
256 * An HTML string containing the formatted item.
257 */
258 function cck_taxonomy_ssu_field_formatter($field, $item, $formatter, $node) {
259 switch ($formatter) {
260 default:
261 $name = db_result(db_query('SELECT name FROM {term_data} WHERE tid = %d', $item['tid']));
262 if ($item['choice']) {
263 return $name. ' - ' .$item['choice'];
264 }
265 else {
266 return $name;
267 }
268 }
269 } // function cck_taxonomy_ssu_field_formatter()
270
271
272 /**
273 * Implementation of hook_widget_info().
274 *
275 * @return
276 * An array keyed by widget name. Each element of the array is an associative
277 * array with these keys and values:
278 * - "label": The human-readable label for the widget.
279 * - "field types": An array of field type names that can be edited using
280 * this widget.
281 */
282 function cck_taxonomy_ssu_widget_info() {
283 $vocabularies = taxonomy_get_vocabularies();
284 $vocabs = array();
285 foreach ($vocabularies as $vid => $vocab) {
286 $vocabs['cck_taxonomy_ssu_' .$vocab->vid] = array(
287 'label' => $vocab->name,
288 'field types' => array('cck_taxonomy_ssu'),
289 );
290 }
291 return $vocabs;
292 } // function cck_taxonomy_ssu_widget_info()
293
294 /**
295 * Implementation of hook_widget_settings().
296 *
297 * @param $op
298 * The operation to be performed.
299 * @param $widget
300 * The widget on which the operation is to be performed.
301 * @return
302 * This varies depending on the operation.
303 * - "form": an array of form elements to add to the settings page.
304 * - "validate": no return value. Use form_set_error().
305 * - "save": an array of names of form elements to be saved in the database.
306 * - "callbacks": an array describing the widget's behaviour regarding hook_widget
307 * operations. The array is keyed by hook_widget operations ('form', 'validate'...)
308 * and has the following possible values :
309 * CONTENT_CALLBACK_NONE : do nothing for this operation
310 * CONTENT_CALLBACK_CUSTOM : use the behaviour in hook_widget(operation)
311 * CONTENT_CALLBACK_DEFAULT : use content.module's default bahaviour
312 * Note : currently only the 'default value' operation implements this feature.
313 * All other widget operation implemented by the module _will_ be executed
314 * no matter what.
315 */
316 function cck_taxonomy_ssu_widget_settings($op, $widget) {
317 switch ($op) {
318 case 'callbacks':
319 return array(
320 'default value' => CONTENT_CALLBACK_CUSTOM,
321 );
322 }
323 } //function cck_taxonomy_ssu_widget_settings()
324
325
326 /**
327 * Implementation of hook_widget().
328 *
329 *
330 * Note: I was running in circles because I didn't realize that 'process form values' is called for both the
331 * 'validate' and 'submit' processes and more importantly, it is called AFTER 'validate' and 'submit'. So
332 * you'll notice code duplicated in the 'submit' protion that is also in the 'process form values' portion
333 * due to this fact. This was counter-intuitive to me.
334 * @param $op
335 * What kind of action is being performed.
336 * @param &$node
337 * The node the action is being performed on.
338 * @param $field
339 * The field the action is being performed on.
340 * @param &$items
341 * The contents of the field in this node. Changes to this variable will
342 * be saved back to the node object.
343 * @return
344 * This varies depending on the operation.
345 * - The "form" operation should return an array of form elements to display.
346 * - Other operations have no return value.
347 */
348 function cck_taxonomy_ssu_widget($op, &$node, $field, &$items) {
349 switch ($op) {
350 case 'prepare form values':
351 $items_transposed = content_transpose_array_rows_cols($items);
352 if ($items_transposed['tid']) {
353 foreach ($items_transposed['tid'] as $key => $value) {
354 if ($value != 0) {
355 $items['default tid'][$value] = $value;
356 if ($items_transposed['choice'] && isset($items_transposed['choice'][$key])) {
357 $items['default choice'][$value] = $items_transposed['choice'][$key];
358 }
359 }
360 unset($items[$key]);
361 }
362 }
363 break;
364
365 case 'form':
366 $vid = substr($field['widget']['type'], CCK_FIELD_NAME_LENGTH);
367 $tss = $field['parents'];// boolean of whether to make the parent categories check-able, or just leaf children
368 $vocabulary = taxonomy_get_vocabulary($vid);
369 $input = $vocabulary->multiple ? 'checkbox' : 'radio';
370
371 // Get root terms for vocabulary only
372 $terms = taxonomy_get_tree($vid, 0, -1, 1);
373 $form[$field['field_name']]['#tree'] = TRUE;
374 $vocabulary->collapsed = 1;
375 $form[$field['field_name']]['tid'] = _cck_taxonomy_ssu_branch($field, $vid, $vocabulary, $vocabulary->tags);
376 $form[$field['field_name']]['tid']['#weight'] = $field['weight'];
377 $form[$field['field_name']]['tid']['#required'] = $field['required'];
378
379 _cck_taxonomy_ssu_next_nested($field, $terms, $vid, $input, $tss, $items, $form[$field['field_name']]['tid'], $vocabulary->tags);
380 //cache_clear_all('content:'. $node->nid .':'. $node->vid, 'cache_content');
381 return $form;
382
383 case 'validate':
384 global $form_values;
385 if (is_array($items['tid'])) {
386 if ($field['required'] == 1) {
387 $choice_made = cck_taxonomy_ssu_validate($items['tid'], $field['field_name'], 1);
388 if ($choice_made != 1) {
389 form_set_error($field['field_name'], t('You must select at least one term.'));
390 }
391 }
392 }
393 break;
394
395 case 'submit':
396 $vid = substr($field['widget']['type'], CCK_FIELD_NAME_LENGTH);
397 if (is_array($items['tid'])) {
398 cck_taxonomy_ssu_submit($items['tid'], $items, $vid);
399 }
400 unset($items['tid']);//remove original data - now is duplicate
401 break;
402
403 case 'process form values'://comes after submit
404 if (is_array($items['tid'])) {
405 cck_taxonomy_ssu_process($items['tid'], $items);
406 }
407 unset($items['tid']);//remove original data - now is duplicate
408 break;
409 }
410 }
411
412 //-----------------------------------------Views Module Integration-------------------------------------
413 //-----------------------------------------Views Module Integration-------------------------------------
414 //-----------------------------------------Views Module Integration-------------------------------------
415
416 /**
417 * Implementation of hook_views_tables as found in cck_taxonomy module. Copied and preserved until I figure
418 * out why the author of the patch didn't use the built-in cck/views integration.
419 */
420 function cck_taxonomy_ssu_views_tables() {
421 $tables = array();
422 // field_name, type_name, widget_type
423 $result = db_query("SELECT * FROM {node_field_instance} WHERE widget_type LIKE 'cck_taxonomy_ssu_%'");
424 while ($row = db_fetch_object($result)) {
425 // Build the list of options
426 $vid = substr($row->widget_type, CCK_FIELD_NAME_LENGTH);
427 $options = array();
428 $td_result = db_query('SELECT tid, name FROM {term_data} WHERE vid = %d', $vid);
429 while ($term = db_fetch_object($td_result)) {
430 $options[$term->tid] = $term->name;
431 }
432
433 $multiple = db_result(db_query('SELECT multiple FROM {vocabulary} WHERE vid = %d', $vid));
434 if ($multiple) {
435 $table_name = 'content_' .$row->field_name;
436 }
437 else {
438 $table_name = 'content_type_' .$row->type_name;
439 }
440
441 # Get the taxonomy multi select property
442 $vocab = taxonomy_get_vocabulary($vid);
443 $multiple = $vocab->multiple;
444
445 $table = array(
446 'name' => $table_name,
447 'provider' => 'cck_taxonomy_ssu',
448 'join' => array(
449 'left' => array(
450 'table' => 'node',
451 'field' => 'vid',
452 ),
453 'right' => array('field' => 'vid'),
454 ),
455 'filters' => array(
456 $row->field_name. '_tid' => array(
457 'name' => t('CCK Taxonomy: @field_name', array('@field_name' => $row->field_name)),
458 'help' => t('Filter on @field_name terms.', array('@field_name' => $row->field_name)),
459 'operator' => 'views_handler_operator_or',
460 'value' => array('#type' => 'select', '#options' => $options,'#multiple' => $multiple),
461 ),
462 ),
463 );
464 $tables['cck_taxonomy_ssu_' .$row->type_name. '_' .$vid] = $table;
465
466 }
467 return $tables;
468 } // function cck_taxonomy_ssu_views_tables()
469
470 //-----------------------------------------Misc Hook Implementations-------------------------------------
471 //-----------------------------------------Misc Hook Implementations-------------------------------------
472 //-----------------------------------------Misc Hook Implementations-------------------------------------
473
474 /**
475 * Implementation of hook_form_alter.
476 *
477 * In the first case, change _content_admin_field where some cck field settings are made. Since vocabulary
478 * was chosen at field creation, and changing it doesn't make sense, disable the radios to select a different
479 * vocabulary. User will have to delete and recreate field in order to change this. Also, adopt the multiple
480 * setting from the vocabulary itself and then disable it.
481 *
482 * Secondly, change taxonomy_form_vocabulary so that vaocabularies need not be assigned to a content type.
483 * This allows the possibility of associating vocabularies with nodes without them being tagged for searches.
484 * Also, directly going to any taxonomy url will not include nodes so associated.
485 *
486 * Example use case: A vocabulary of interests in a nodeprofile node where you want your own search mechanism
487 * to sift and show what a user has permission to see, rather than the whole node.
488 */
489 function cck_taxonomy_ssu_form_alter($form_id, &$form) {
490 if ($form_id == '_content_admin_field') {
491 if (isset($form['#programmed']) && $form['#programmed'] == TRUE) {
492 //TODO see cck_taxonomy_ssu_taxonomy()::update to see where _content_admin_field is called programmatically
493 $form['#validate']['cck_taxonomy_ssu_content_admin_field_validate'] = array();
494 }
495 else {
496 if ($form['field_type']['#value'] == 'cck_taxonomy_ssu') {
497 // Disable this since there will always only be one choice
498 $form['widget']['widget_type']['#disabled'] = TRUE;
499
500 // Take the multiple setting from the vocabulary.
501 $vid = substr($form['widget']['widget_type']['#default_value'], CCK_FIELD_NAME_LENGTH);
502 $vocab = taxonomy_get_vocabulary($vid);
503 $form['field']['multiple']['#disabled'] = TRUE;
504 $form['field']['multiple']['#value'] = $vocab->multiple;
505 }
506 }
507 }
508 // Change the behavior of the taxonomy vocabulary form so that it isn't
509 // required to assign a content type to a vocabulary.
510 if ($form_id == 'taxonomy_form_vocabulary') {
511 $form['nodes']['#required'] = FALSE;
512 }
513 } // function cck_taxonomy_ssu_form_alter()
514
515
516 /**
517 * This handler is only added to #programmed instances of the _content_admin_field
518 * form. In those cases the updated value of 'multiple' is stuck in the #post
519 * array.
520 */
521 function cck_taxonomy_ssu_content_admin_field_validate($form_id, $form_values, $form) {
522 form_set_value($form['field']['multiple'], $form['field']['multiple']['#post']['field']['multiple']['#value']);
523 } // function cck_taxonomy_ssu_content_admin_field_validate()
524
525
526 /**
527 * Because the cck_taxonomy_ssu widgets are dependent on the definition of the
528 * taxonomy vocabulary, if the vocabulary changes, we have to update the
529 * field definitions as well.
530 */
531 function cck_taxonomy_ssu_taxonomy($op, $type, $array = NULL) {
532 if ($type == 'vocabulary') {
533 // Find out which widget we're dealing with.
534 $widget_type = 'cck_taxonomy_ssu_' .$array['vid'];
535 // Get all the field instances that use the affected widget.
536 $result = db_query("SELECT field_name, type_name FROM {node_field_instance} WHERE widget_type = '%s'", $widget_type);
537 switch ($op) {
538 case 'update':
539 // A vocabulary has been updated. This has potential repercussions on
540 // the CCK fields. Thus we need to re-submit the field configuration
541 // forms with the new values. For each field instance, get the field
542 // configuration form, update its values, and re-submit it using drupal_execute.
543 while ($row = db_fetch_object($result)) {
544 // Getting the form is easy.
545 $form = _content_admin_field($row->type_name, $row->field_name);
546 // Updating the value *looks* easy, but this isn't the end of the story.
547 // This will get stuck in the #post part of the field in the built
548 // form and ignored. Therefore, there is an extra validation handler
549 // for _content_admin_field (see cck_taxonomy_ssu_form_alter and
550 // cck_taxonomy_ssu_content_admin_field_validate). This handler uses
551 // form_set_value to update the global $form_values at the appropriate
552 // time. Very convoluted.
553 $form['field']['multiple']['#value'] = $array['multiple'];
554 drupal_execute('_content_admin_field', $form, $row->type_name, $row->field_name);
555 }
556 break;
557
558 case 'delete':
559 // Delete the fields that are based on this vocabulary.
560 while ($row = db_fetch_object($result)) {
561 $form = _content_admin_field_remove($row->type_name, $row->field_name);
562 drupal_execute('_content_admin_field_remove', $form, $row->type_name, $row->field_name);
563 }
564 break;
565 }
566 }
567 } // function cck_taxonomy_ssu_taxonomy()
568
569 //-----------------------------------------Helper Functions-------------------------------------
570 //-----------------------------------------Helper Functions-------------------------------------
571 //-----------------------------------------Helper Functions-------------------------------------
572
573 /**
574 * Helper function to pull a vid from a field name string.
575 *
576 * Since every instance of this field is associated with a particular vocabulary,
577 * that vocabulary's number is appended to the end of the cck field name of
578 * cck_taxonomy_ssu_. For example, cck_taxonomy_ssu_4 would indicate a vid of 4.
579 * This may be problematic in the case where the same vocabulary is wanted to be
580 * used in more than one cck field with different settings. For now, don't do it.
581 */
582 function cck_taxonomy_ssu_get_vid($field) {
583 if ($field['widget'] && $field['widget']['type']) {
584 return substr($field['widget']['type'], CCK_FIELD_NAME_LENGTH);
585 }
586 } // function cck_taxonomy_ssu_get_vid()
587
588
589 /**
590 * Recursive function to allow infinite depth vocabularies to be displayed as fieldsets and
591 * checkboxes.
592 *
593 * @param array &$terms The result of taxonomy_get_tree for the vocabulary's $vid.
594 * @param array $vid The vocabulary to work on.
595 * @param string $input 'checkbox' or 'radio'.
596 * @param array $tss These are the settings for this module.
597 * @param array &$items This holds the default_values/stored values of terms.
598 * @param array &$form_branch This is a subsection of $form. Each iteration adds to the one
599 * before, then passes itself as the new branch. _cck_taxonomy_ssu_branch() is called for each iteration
600 * and appended to it.
601 *
602 * @return since values are passed in via reference, no return value is required.
603 */
604 function _cck_taxonomy_ssu_next_nested($field, &$terms, $vid, $input, $tss, &$items, &$form_branch, $freetag) {
605 $fieldweight = -1;
606 foreach ($terms as $index => $term) {
607 $child = taxonomy_get_children($term->tid, $vid);
608 if (count($child)) {
609 if ($tss) {
610 $term->is_parent = TRUE;
611 $term->parent_type = $input;
612 $term->parent_value = $items['default tid'][$term->tid];
613 if ($items['default choice']) {
614 $term->choice_value = $items['default choice'][$term->tid];
615 }
616 }
617 $form_branch[$term->tid] = _cck_taxonomy_ssu_branch($field, $vid, $term, $freetag, NULL, NULL, 'fieldset', $fieldweight++);
618 _cck_taxonomy_ssu_next_nested($field, $child, $vid, $input, $tss, $items, $form_branch[$term->tid], $freetag);
619 }
620 else{
621 if ($value = $items['default tid'][$term->tid]) {
622 $form_branch[$term->tid]['#collapsed'] = FALSE;
623 }
624 if ($field['rate_yesno'] && $items['default choice']) {
625 $form_branch[$term->tid] = _cck_taxonomy_ssu_branch($field, $vid, $term, $freetag, $value, $items['default choice'][$term->tid], $input, $fieldweight++);
626 }
627 else {
628 $form_branch[$term->tid] = _cck_taxonomy_ssu_branch($field, $vid, $term, $freetag, $value, NULL, $input, $fieldweight++);
629 }
630 }
631 }
632 }
633
634
635 /**
636 * Create the checkboxes or radio buttons. Terms should be alphabetized as follows: parent of current level, nested parents, terms of current level
637 *
638 * @param int $vid The ID of the vocabulary in question.
639 * @param object $term The vocabulary term to be worked on.
640 * @param int $value Default is NULL and is not needed if $type is 'fieldset'. Otherwise, this is the term id.
641 * @param string $type The type of form element to create. Possibilities are 'filedset', 'radio' and 'checkbox'.
642 * 'fieldset' is the default. If 'radio' or 'checkbox' is selected, $value should be set.
643 * @param int $fieldweight This aids in the alphabetizing of the display. Only use this if you are going to override
644 * _cck_taxonomy_ssu_next_nested(). Otherwise, when you call this function, leave it to the default of -1.
645 * @return array $form Since the form is not passed in by reference, we need to return the work done to the calling
646 * function.
647 */
648 function _cck_taxonomy_ssu_branch($field, $vid, $term, $freetag = 0, $value = NULL, $choice_value = NULL, $type = 'fieldset', $fieldweight = -1) {
649 $required = $field['required'] ? ' <span class="form-required" title="' .t('This field is required.'). '">*</span>' : '';
650 switch ($type) {
651 case 'fieldset':
652 // Automatically expand required vocabs or if the parent term is selected
653 $other_collapse = TRUE;
654 $collapsed = isset($term->collapsed) ? FALSE : $other_collapse;//this will open the first fieldset all the time, but open all children dependent on required-ness
655 $form = array(
656 '#type' => 'fieldset',
657 '#title' => t($term->name).$required,
658 '#collapsible' => TRUE,
659 '#collapsed' => $collapsed,
660 '#weight' => $fieldweight-1000,//for proper alphabetizing
661 '#description' => t($term->description),
662 '#attributes' => array('class' => 'cck-tssu'),
663 );
664 if ($freetag) {
665 $default_value = NULL;
666 if ($term->module == 'taxonomy' || $term->module == 'og_vocab') {//handle top level
667 if (!$field['top_level_freetag']) {//users not restricted from adding new top level branches
668 if (!$field['no_freetag_branching']) {//if set to 0, show parent and leaves in selector
669 $form['select_parent'] = _cck_taxonomy_ssu_term_select(t('Parent'),
670 'select_parent',
671 $default_value,
672 $vid,
673 l(t('Select parent term to put your new term inside'), 'admin/help/taxonomy', NULL, NULL, 'parent'). '.',
674 0,
675 '<' .t('root'). '>',
676 1);
677 }
678 else {//if set to 1, show only parent in selector
679 $form['select_parent'] = _cck_taxonomy_ssu_term_select(t('Parent'),
680 'select_parent',
681 $default_value,
682 $vid,
683 l(t('Select parent term to put your new term inside'), 'admin/help/taxonomy', NULL, NULL, 'parent'). '.',
684 0,
685 '<' .t('root'). '>',
686 1,
687 1);
688 }
689 $form['term_name'] = array(
690 '#type' => 'textfield',
691 '#title' => t('Add a Term'),
692 '#default_value' => '',
693 '#size' => 30,
694 '#required' => FALSE,
695 '#maxlength' => 255,
696 '#description' => t('Don\'t see a term that fits what you\'re looking for? Add your own. Select which term should be its parent in the drop down list above. Separate multiple terms by a comma.'),
697 '#weight' => 1000,
698 );
699 }
700 else {//users cannot add new top level branches
701 if (!$field['no_freetag_branching']) {//if set to 0, show parent and leaves in selector
702 $form['select_parent'] = _cck_taxonomy_ssu_term_select(t('Parent'),
703 'select_parent',
704 $default_value,
705 $vid,
706 l(t('Select parent term to put your new term inside'), 'admin/help/taxonomy', NULL, NULL, 'parent'). '.',
707 0,
708 '<' .t('none'). '>');
709 $form['term_name'] = array(
710 '#type' => 'textfield',
711 '#title' => t('Add a Term'),
712 '#default_value' => '',
713 '#size' => 30,
714 '#required' => FALSE,
715 '#maxlength' => 255,
716 '#description' => t('Don\'t see a term that fits what you\'re looking for? Add your own. Select which term should be its parent in the drop down list above. Separate multiple terms by a comma.'),
717 '#weight' => 1000,
718 );
719 }
720 }
721 }
722 else {
723 if (!$field['no_freetag_branching']) {//if set to 0, show parent and leaves in selector
724 $form['select_parent'] = _cck_taxonomy_ssu_term_select(t('Parent'),
725 'select_parent',
726 $default_value,
727 $term->tid,
728 l(t('Select parent term to put your new term inside'), 'admin/help/taxonomy', NULL, NULL, 'parent'). '.',
729 0,
730 '<' .t('none'). '>');
731 }
732 else {//if set to 1, show only parent in selector
733 $form['select_parent'] = _cck_taxonomy_ssu_term_select(t('Parent'),
734 'select_parent',
735 $default_value,
736 $term->tid,
737 l(t('Select parent term to put your new term inside'), 'admin/help/taxonomy', NULL, NULL, 'parent'). '.',
738 0,
739 '<' .t('none'). '>',
740 0,
741 1);
742 }
743 $form['term_name'] = array(
744 '#type' => 'textfield',
745 '#title' => t('Add a Term'),
746 '#default_value' => '',
747 '#size' => 30,
748 '#required' => FALSE,
749 '#maxlength' => 255,
750 '#description' => t('Don\'t see a term that fits what you\'re looking for? Add your own. Select which term should be its parent in the drop down list above. Separate multiple terms by a comma.'),
751 '#weight' => 1000,
752 );
753 }
754 }
755 // If we have vocabulary that is single select and not required or is freetagging we need a way to unselect the term
756 if ((!$required OR $term->tags) AND $term->multiple == 0 AND ($term->module == 'taxonomy' || $term->module == 'og_vocab')) {
757 $form['none'] = array(
758 '#type' => 'radio',
759 '#title' => '<em>' .t('Select None'). '</em>',
760 '#return_value' => -1,
761 '#default_value' => NULL,
762 '#weight' => -10001,
763 '#parents' => array('tid'),
764 );
765 }
766 if ($term->is_parent) {
767 $form['parent'] = _cck_taxonomy_ssu_branch($field, $vid, $term, $freetag, $term->parent_value, $term->choice_value, $term->parent_type);
768 $form['parent']['#weight'] = -10000;//for proper alphabetizing
769 }
770 break;
771 case 'radio':
772 if ($field['rate_yesno']) {
773 $form['set'] = array(
774 '#type' => 'fieldset',
775 '#weight' => $fieldweight,
776 );
777 }
778 $form['set'][] = array(
779 '#type' => 'radio',
780 '#title' => ($term->is_parent ? '<strong>' : '').t($term->name).($term->is_parent ? '</strong>' : ''),
781 '#return_value' => $term->tid,
782 '#default_value' => $value,
783 '#weight' => $fieldweight,
784 '#prefix' => $field['rate_yesno'] ? '<span style="clear:left; float:left; margin-right:2em;">' : '',
785 '#suffix' => $field['rate_yesno'] ? '</span>' : '',
786 '#parents' => array('tid'),
787 );
788 if ($field['rate_yesno']) {
789 $choice_title = $field['choice_title'];
790 $options = cck_taxonomy_ssu_choice_values($field);
791 $form['set'][] = array(
792 '#type' => 'select',
793 '#options' => $options,
794 '#default_value' => $choice_value,
795 '#weight' => $fieldweight+1,
796 '#prefix' => '<span style="clear:right;">',
797 '#suffix' => '</span>',
798 '#description' => $field['choice_title'],
799 );
800 }
801 break;
802 case 'checkbox':
803 if ($field['rate_yesno']) {
804 $form['set'] = array(
805 '#type' => 'fieldset',
806 '#weight' => $fieldweight,
807 );
808 }
809 $form['set'][] = array(
810 '#type' => 'checkbox',
811 '#title' => ($term->is_parent ? '<strong>' : '').t($term->name).($term->is_parent ? '</strong>' : ''),
812 '#return_value' => $term->tid,
813 '#default_value' => $value,
814 '#weight' => $fieldweight,
815 '#prefix' => $field['rate_yesno'] ? '<span style="clear:left; float:left; margin-right:2em;">' : '',
816 '#suffix' => $field['rate_yesno'] ? '</span>' : '',
817 );
818 if ($field['rate_yesno']) {
819 $choice_title = $field['choice_title'];
820 $options = cck_taxonomy_ssu_choice_values($field);
821 $form['set'][] = array(
822 '#type' => 'select',
823 '#options' => $options,
824 '#default_value' => $choice_value,
825 '#weight' => $fieldweight+1,
826 '#prefix' => '<span style="clear:right;">',
827 '#suffix' => '</span>',
828 '#description' => $field['choice_title'],
829 );
830 }
831 break;
832 }
833 return $form;
834 }
835
836 /**
837 * Create an array of the allowed values for this field
838 */
839 function cck_taxonomy_ssu_choice_values($field) {
840 static $allowed_values;
841
842 if ($allowed_values[$field['field_name']]) {
843 return $allowed_values[$field['field_name']];
844 }
845 $allowed_values[$field['field_name']] = array();
846 if (!$allowed_values[$field['field_name']]) {
847 $list = explode("\n", $field['choices']);
848 $list = array_map('trim', $list);
849 $list = array_filter($list, 'strlen');
850 foreach ($list as $opt) {
851 list($key, $value) = explode('|', $opt);
852 $allowed_values[$field['field_name']][$key] = $value ? $value : $key;
853 }
854 }
855 return $allowed_values[$field['field_name']];
856 }
857
858 /**
859 * Process the array of checked items into a form that cck expects.
860 *
861 * It expects $items[$delta] = array('field_name' => $value), so in our case, we need something like:
862 * $items[16] = array('tid' => 16) if our checked item has a tid of 16.
863 *
864 * @param array &$item This is a portion of the overall &$items array. We can't just pass in the whole
865 * &$items array for processing because of the first foreach statement which looks for $tid's; there
866 * are no $tid's at the top level of &$items. Plus, for recursion, we are providing smaller and smaller
867 * pieces of the array, each of which has $tid's as index elements.
868 * @param array &$items This is the top level because we need to write to it for what cck expects.
869 */
870 function cck_taxonomy_ssu_process(&$item, &$items) {
871 foreach ($item as $tid => $value) {
872 if (is_array($item[$tid])) {
873 $items[$tid]['select_parent'] = $item[$tid]['select_parent'];
874 $items[$tid]['term_name'] = $item[$tid]['term_name'];
875 unset($item[$tid]['term_name']);
876 unset($item[$tid]['select_parent']);
877 if ($tid == 'set' && ($item[$tid][0] != 0)) {
878 $items[$item[$tid][0]]['choice'] = $item[$tid][1];
879 }
880 cck_taxonomy_ssu_process($item[$tid], $items);
881 }
882 else {
883 if (($value == 0) || ($value == NULL)) {
884 unset($item[$tid]);
885 }
886 elseif (is_numeric($value)) {
887 $items[$value] = array('tid' => $value);
888 unset($item[$tid]);
889 }
890 }
891 }
892 unset($item);
893 return;
894 } // function cck_taxonomy_ssu_process()
895
896 function cck_taxonomy_ssu_validate(&$item, $field_name, $reset = 0) {
897 static $choice_made;
898 if ($reset == 1) {
899 $choice_made = 0;
900 }
901 //take care of the initial case - the top level of tid for new term insertion into the vocabulary
902 if (is_array($item) && isset($item['select_parent']) && (($item['select_parent'] != 0) || ($item['select_parent'] == 'X')) && ($item['term_name'] != '')) {
903 $choice_made = 1;
904 }
905 elseif (is_array($item) && isset($item['select_parent']) && $item['select_parent'] == 0 && $item['term_name'] != '') {
906 form_set_error($field_name, t('You must select a parent for the term you are trying to add.'));
907 }
908 foreach ($item as $tid => $value) {
909 //now handle the rest of the cases for new term insertion into the vocabulary (free tagging)
910 if (is_array($item[$tid])) {
911 if (($item[$tid]['select_parent'] != NULL) && ($item[$tid]['select_parent'] != 0) && ($item[$tid]['term_name'] != '')) {
912 $choice_made = 1;
913 }
914 elseif ($item[$tid]['term_name'] != '') {
915 form_set_error($field_name, t('You must select a parent for the term you are trying to add.'));
916 }
917 if (isset($item[$tid]['set'][0]) && ($item[$tid]['set'][0] != 0)) {
918 $choice_made = 1;
919 }
920 $choice_made = cck_taxonomy_ssu_validate($item[$tid], $field_name);
921 }
922 }
923 return $choice_made;
924 } // function cck_taxonomy_ssu_validate()
925
926 /**
927 * Submit the array of checked items into a form that cck expects.
928 *
929 * @param array &$item This is a portion of the overall &$items array. We can't just pass in the whole
930 * &$items array for processing because of the first foreach statement which looks for $tid's; there
931 * are no $tid's at the top level of &$items. Plus, for recursion, we are providing smaller and smaller
932 * pieces of the array, each of which has $tid's as index elements.
933 * @param array &$items This is the top level because we need to write to it for what cck expects.
934 */
935 function cck_taxonomy_ssu_submit(&$item, &$items, $vid) {
936 $rex = "/,+\s*/";
937 //take care of the initial case - the top level of tid for new term insertion into the vocabulary
938 if (is_array($item) && isset($item['select_parent']) && (($item['select_parent'] != 0) || ($item['select_parent'] == 'X')) && ($item['term_name'] != '')) {
939 if ($item['select_parent'] == 'X') {
940 $item['select_parent'] = 0;
941 }
942 //check to see if there are multiple new terms; if so, break them into an array of terms
943 $new_terms = preg_split($rex, $item['term_name']);
944 if (!empty($new_terms)) {//if the array was made and filled, handle each new term
945 foreach ($new_terms as $index => $term) {
946 if (db_fetch_object(db_query("SELECT td.tid FROM {term_data} td INNER JOIN {term_hierarchy} h WHERE h.parent = %d AND td.name = '%s' AND td.vid = %d", $item['select_parent'], $term, $vid))) {
947 continue;
948 }
949 else {
950 //construct array to pass to taxonomy_save_term; this should allow pathauto to work; repeat below as necessary
951 $term_data = array('name' => $term, 'vid' => $vid, 'description' => '', 'parent' => $item['select_parent']);
952 taxonomy_save_term($term_data);
953 $sql = "SELECT tid FROM {term_data} WHERE vid = %d and name = '%s'";
954 $term_tid = db_fetch_object(db_query($sql, $vid, $term));
955 $term_tid = $term_tid->tid;
956 $item[$term_tid]['set'][0] = $term_tid;//add the new tid to the $item so it can be 'checkedboxed', otherwise user will have to edit the page again and click the box
957 $items[$term_tid] = array('tid' => $term_tid);
958 }
959 }
960 unset($item['term_name']);
961 unset($item['select_parent']);
962 }
963 else {
964 if (db_fetch_object(db_query("SELECT td.tid FROM {term_data} td INNER JOIN {term_hierarchy} h WHERE h.parent = %d AND td.name = '%s' AND td.vid = %d", $item['select_parent'], $item['term_name'], $vid))) {
965 unset($item['term_name']);
966 unset($item['select_parent']);
967 continue;
968 }
969 else {
970 //construct array to pass to taxonomy_save_term; this should allow pathauto to work; repeat below as necessary
971 $term_data = array('name' => $item['term_name'], 'vid' => $vid, 'description' => '', 'parent' => $item['select_parent']);
972 taxonomy_save_term($term_data);
973 $sql = "SELECT tid FROM {term_data} WHERE vid = %d and name = '%s'";
974 $term_tid = db_fetch_object(db_query($sql, $vid, $item['term_name']));
975 $term_tid = $term_tid->tid;
976 $item[$term_tid]['set'][0] = $term_tid;//add the new tid to the $item so it can be 'checkedboxed', otherwise user will have to edit the page again and click the box
977 $items[$term_tid] = array('tid' => $term_tid);
978 unset($item['term_name']);
979 unset($item['select_parent']);
980 }
981 }
982 }
983 else {
984 unset($item['term_name']);
985 unset($item['select_parent']);
986 }
987
988 foreach ($item as $tid => $value) {
989 if (is_array($item[$tid])) {
990 if (($item[$tid]['select_parent'] != NULL) && ($item[$tid]['select_parent'] != 0) && ($item[$tid]['term_name'] != '')) {//now handle the rest of the cases for new term insertion into the vocabulary (free tagging)
991 //check to see if there are multiple new terms; if so, break them into an array of terms
992 $new_terms = preg_split($rex, $item[$tid]['term_name']);
993 if (!empty($new_terms)) {//if the array was made and filled, handle each new term
994 foreach ($new_terms as $index => $term) {
995 if (db_fetch_object(db_query("SELECT td.tid FROM {term_data} td INNER JOIN {term_hierarchy} h WHERE h.parent = %d AND td.name = '%s' AND td.vid = %d", $item[$tid]['select_parent'], $term, $vid))) {
996 continue;
997 }
998 else {
999 //construct array to pass to taxonomy_save_term; this should allow pathauto to work
1000 $term_data = array('name' => $term, 'vid' => $vid, 'description' => '', 'parent' => $item[$tid]['select_parent']);
1001 taxonomy_save_term($term_data);
1002 $sql = "SELECT tid FROM {term_data} WHERE vid = %d and name = '%s'";
1003 $term_tid = db_fetch_object(db_query($sql, $vid, $term));
1004 $term_tid = $term_tid->tid;
1005 $item[$tid][$term_tid]['set'][0] = $term_tid;//add the new tid to the $item so it can be 'checkedboxed', otherwise user will have to edit the page again and click the box
1006 //I'm not sure of the best way to set the default rating if ratings are used. Currently not setting it.
1007 $items[$term_tid] = array('tid' => $term_tid);
1008 }
1009 }
1010 unset($item[$tid]['term_name']);
1011 unset($item[$tid]['select_parent']);
1012 }
1013 else {
1014 if (db_fetch_object(db_query("SELECT td.tid FROM {term_data} td INNER JOIN {term_hierarchy} h WHERE h.parent = %d AND td.name = '%s' AND td.vid = %d", $item[$tid]['select_parent'], $item[$tid]['term_name'], $vid))) {
1015 unset($item[$tid]['term_name']);
1016 unset($item[$tid]['select_parent']);
1017 continue;
1018 }
1019 else {
1020 //construct array to pass to taxonomy_save_term; this should allow pathauto to work
1021 $term_data = array('name' => $item[$tid]['term_name'], 'vid' => $vid, 'description' => '', 'parent' => $item[$tid]['select_parent']);
1022 taxonomy_save_term($term_data);
1023 $sql = "SELECT tid FROM {term_data} WHERE vid = %d and name = '%s'";
1024 $term_tid = db_fetch_object(db_query($sql, $vid, $item[$tid]['term_name']));
1025 $term_tid = $term_tid->tid;
1026 $item[$tid][$term_tid]['set'][0] = $term_tid;//add the new tid to the $item so it can be 'checkedboxed', otherwise user will have to edit the page again and click the box
1027 //I'm not sure of the best way to set the default rating if ratings are used. Currently not setting it.
1028 $items[$term_tid] = array('tid' => $term_tid);
1029 unset($item[$tid]['term_name']);
1030 unset($item[$tid]['select_parent']);
1031 }
1032 }
1033 }
1034 else {
1035 unset($item[$tid]['term_name']);
1036 unset($item[$tid]['select_parent']);
1037 }
1038
1039 if (is_array($item[$tid]['parent']) && $item[$tid]['parent']['set'][0] != 0) {
1040 $val = $item[$tid]['parent']['set'][0];
1041 $items[$val] = array('tid' => $val);
1042 if (!empty($item[$tid]['parent']['set'][1])) {//Handle the multi-choice options.
1043 $items[$val]['choice'] = $item[$tid]['parent']['set'][1];
1044 }
1045 unset($item[$tid]['parent']);
1046 }
1047 elseif (is_array($item[$tid]['set']) && $item[$tid]['set'][0] != 0) {
1048 $val = $item[$tid]['set'][0];
1049 $items[$val] = array('tid' => $val);
1050 if (!empty($item[$tid]['set'][1])) {//Handle the multi-choice options.
1051 $items[$val]['choice'] = $item[$tid]['set'][1];
1052 }
1053 unset($item[$tid]);
1054 }
1055 elseif (is_array($item[$tid]['set']) && $item[$tid]['set'][0] == 0) {
1056 unset($item[$tid]);
1057 }
1058 else {
1059 cck_taxonomy_ssu_submit($item[$tid], $items, $vid);
1060 }
1061 }
1062 }
1063 unset($item);
1064 return;
1065 } // function cck_taxonomy_ssu_submit()
1066
1067 /**
1068 * Get a subset of taxonomy terms and put them in a select box.
1069 */
1070 function _cck_taxonomy_ssu_term_select($title, $name, $value, $tid, $description, $multiple, $blank, $vid = FALSE, $no_branching = FALSE, $exclude = array()) {
1071 if ($vid) {//if we're at the top level, do something a little different
1072 $tree = taxonomy_get_tree($tid, 0, -1, 1);
1073 }
1074 else {
1075 $tree = array_merge(array($tid => taxonomy_get_term($tid)), taxonomy_get_children($tid));
1076 }
1077 $options = array();
1078
1079 if ($blank == ('<' .t('root'). '>')) {
1080 $choice = new stdClass();
1081 $choice->option = array('X' => $blank);
1082 $options[0] = $choice;
1083 }
1084 else {
1085 $options[0] = $blank;
1086 }
1087 if ($tree && $no_branching != 1) {
1088 foreach ($tree as $term) {
1089 if (!in_array($term->tid, $exclude)) {
1090 $choice = new stdClass();
1091 //the stars mark the current level so that users can easily make sure they are placing a new term at the same level as its siblings
1092 $choice->option = array($term->tid => str_repeat('-', $term->depth).($term->tid == $tid ? '*' : '').t($term->name).($term->tid == $tid ? '*' : ''));
1093 $options[] =