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

Contents of /contributions/modules/fieldreference/fieldreference.module

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


Revision 1.10 - (show annotations) (download) (as text)
Sun Sep 7 15:32:22 2008 UTC (14 months, 3 weeks ago) by guardian
Branch: MAIN
CVS Tags: HEAD
Changes since 1.9: +5 -4 lines
File MIME type: text/x-php
adding optgroup support to select list widget
1 <?php
2 // $Id: fieldreference.module,v 1.9 2008/08/05 10:29:05 guardian Exp $
3
4 /**
5 * @file
6 * Defines a field type for referencing a field.
7 */
8
9 /**
10 * Implementation of hook_menu().
11 */
12 function fieldreference_menu($may_cache) {
13 $items = array();
14
15 if ($may_cache) {
16 $items[] = array(
17 'path' => 'fieldreference/autocomplete',
18 'title' => t('field reference autocomplete'),
19 'callback' => 'fieldreference_autocomplete',
20 'access' => user_access('access content'),
21 'type' => MENU_CALLBACK
22 );
23 }
24
25 return $items;
26 }
27
28 /**
29 * Implementation of hook_field_info().
30 */
31 function fieldreference_field_info() {
32 return array(
33 'fieldreference' => array('label' => t('Field Reference')),
34 );
35 }
36
37 /**
38 * Implementation of hook_field_settings().
39 */
40 function fieldreference_field_settings($op, $field) {
41 switch ($op) {
42 case 'form':
43 $form = array();
44 $form['referenceable_types'] = array(
45 '#type' => 'checkboxes',
46 '#title' => t('Fields that can be referenced'),
47 '#multiple' => TRUE,
48 '#default_value' => isset($field['referenceable_types']) ? $field['referenceable_types'] : array(),
49 '#options' => _fieldreference_get_fields()
50 );
51 return $form;
52
53 case 'save':
54 return array('referenceable_types');
55
56 case 'database columns':
57 $columns = array(
58 'nid' => array('type' => 'int', 'not null' => TRUE, 'default' => '0'),
59 'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => "''"),
60 'delta' => array('type' => 'int', 'not null' => TRUE, 'default' => '0')
61 );
62 return $columns;
63
64 case 'filters':
65 return array(
66 'default' => array(
67 'list' => '_fieldreference_filter_handler',
68 'list-type' => 'list',
69 'operator' => 'views_handler_operator_or',
70 'value-type' => 'array',
71 'extra' => array('field' => $field),
72 ),
73 );
74 }
75 }
76
77 /**
78 * Return an array of all fields
79 */
80 function _fieldreference_get_fields() {
81 $fields = array();
82 $result = db_query('SELECT nt.type, nfi.field_name, nt.name, nfi.label
83 FROM {node_type} nt INNER JOIN {node_field_instance} nfi ON nt.type = nfi.type_name
84 ORDER BY nt.name, nfi.label');
85
86 while ($row = db_fetch_object($result)) {
87 $fields[$row->type . ':' . $row->field_name] = $row->name . ':' . $row->label;
88 }
89
90 return $fields;
91 }
92
93 /**
94 * Implementation of hook_field().
95 */
96 function fieldreference_field($op, &$node, $field, &$items, $teaser, $page) {
97 switch ($op) {
98 case 'validate':
99 if (!is_array($items['fields'])) {
100 return;
101 }
102
103 $refs = array_keys(_fieldreference_potential_references($field));
104 $fields = array_values($items['fields']);
105 unset($items['fields']);
106 foreach ($items as $delta => $item) {
107 $error_field = isset($item['error_field']) ? $item['error_field'] : '';
108 unset($item['error_field']);
109 if (!empty($fields[$delta])) {
110 if (!in_array($fields[$delta], $refs)) {
111 form_set_error($error_field, t('%name : This field can\'t be referenced.', array('%name' => t($field['widget']['label']))));
112 }
113 }
114 }
115 return;
116
117 default:
118 unset($items['fields']);
119 }
120 }
121
122 /**
123 * Implementation of hook_field_formatter_info().
124 */
125 function fieldreference_field_formatter_info() {
126 return array(
127 'default' => array(
128 'label' => 'Default',
129 'field types' => array('fieldreference')
130 ),
131 'full' => array(
132 'label' => t('Full'),
133 'field types' => array('fieldreference')
134 ),
135 'teaser' => array(
136 'label' => t('Teaser'),
137 'field types' => array('fieldreference')
138 )
139 );
140 }
141
142 /**
143 * Implementation of hook_field_formatter().
144 */
145 function fieldreference_field_formatter($field, $item, $formatter, $node) {
146 static $recursion_queue = array();
147
148 if (empty($item['nid']) || !is_numeric($item['nid']) ||
149 empty($item['field_name']) ||
150 !is_numeric($item['delta'])) {
151 return '';
152 }
153
154 $field_id = $item['nid'] . ':' . $item['field_name'] . ':' . $item['delta'];
155 if (in_array($field_id, $recursion_queue)) {
156 drupal_set_message(t('circular field reference chain'), 'error');
157 return '';
158 }
159
160 if ($formatter == 'default') {
161 $context = $node->teaser ? 'teaser' : 'full';
162 }
163 else {
164 $context = $formatter;
165 }
166
167 $referenced_node = node_load($item['nid']);
168 if (!isset($referenced_node->$item['field_name'])) {
169 return '';
170 }
171
172 $referenced_type = content_types($referenced_node->type);
173 $referenced_field = $referenced_type['fields'][$item['field_name']];
174
175 if ($referenced_field['type'] == 'fieldreference') {
176 $recursion_queue[] = $field_id;
177 }
178 else {
179 $recursion_queue = array();
180 }
181
182 $referenced_formatter = isset($referenced_field['display_settings'][$context]['format']) ? $referenced_field['display_settings'][$context]['format'] : 'default';
183
184 $referenced_items = $referenced_node->$item['field_name'];
185
186
187 return content_format($referenced_field, $referenced_items[$item['delta']], $referenced_formatter, $referenced_node);
188 }
189
190 /**
191 * Implementation of hook_widget_info().
192 */
193 function fieldreference_widget_info() {
194 return array(
195 'fieldreference_select' => array(
196 'label' => 'Select List',
197 'field types' => array('fieldreference'),
198 ),
199 'fieldreference_autocomplete' => array(
200 'label' => t('Autocomplete Text Field'),
201 'field types' => array('fieldreference')
202 )
203 );
204 }
205
206 /**
207 * Implementation of hook_widget().
208 */
209 function fieldreference_widget($op, &$node, $field, &$items) {
210 if ($field['widget']['type'] == 'fieldreference_select') {
211 switch ($op) {
212 case 'prepare form values':
213 $options = array();
214 foreach($items as $delta => $item) {
215 $options[] = $item['nid'] . ':' . $item['field_name'] . ':' . $item['delta'];
216 }
217 $items['default fields'] = $options;
218 break;
219
220 case 'form':
221 $form = array();
222
223 $values = _fieldreference_potential_references($field);
224 $options = array();
225 foreach ($values as $key => $value) {
226 $options[$value->title][$key] = _fieldreference_item($field, $value, FALSE);
227 }
228 if (!$field['required']) {
229 $options = array(0 => t('<none>')) + $options;
230 }
231 $form[$field['field_name']] = array('#tree' => TRUE);
232 $form[$field['field_name']]['fields'] = array(
233 '#type' => 'select',
234 '#title' => t($field['widget']['label']),
235 '#default_value' => $items['default fields'],
236 '#multiple' => $field['multiple'],
237 '#size' => $field['multiple'] ? min(count($options), 6) : 0,
238 '#options' => $options,
239 '#required' => $field['required'],
240 '#description' => t($field['widget']['description']),
241 );
242
243 return $form;
244
245 case 'process form values':
246 if ($field['multiple']) {
247 // if nothing selected, make it 'none'
248 if (empty($items['fields'])) {
249 $items['fields'] = array(0 => '0');
250 }
251 // drop the 'none' options if other items were also selected
252 elseif (count($items['fieds']) > 1) {
253 unset($items['nids'][0]);
254 }
255
256 $storage = array();
257 foreach($items['fields'] as $value) {
258 $value = explode(':', $value);
259 if (!$value) {
260 continue;
261 }
262 $delta['nid'] = $value[0];
263 $delta['field_name'] = $value[1];
264 $delta['delta'] = $value[2];
265 $delta['error_field'] = $field['field_name'] .'][fields';
266 $storage[] = $delta;
267 }
268 $storage['fields'] = $items['fields'];
269 $items = $storage;
270 }
271 else {
272 $value = explode(':', $items['fields']);
273 if (!$value) {
274 unset($items[0]);
275 }
276 $items[0]['nid'] = $value[0];
277 $items[0]['field_name'] = $value[1];
278 $items[0]['delta'] = $value[2];
279 $items[0]['error_field'] = $field['field_name'] .'][fields';
280 }
281 break;
282 }
283 }
284 else if ($field['widget']['type'] == 'fieldreference_autocomplete') {
285 switch($op) {
286 case 'prepare form values':
287 foreach ($items as $delta => $item) {
288 if (!empty($item['nid']) && !empty($item['field_name']) && is_numeric($item['delta'])) {
289 $query = "SELECT n.title AS title, nfi.label AS label FROM {node} n
290 INNER JOIN {node_field_instance} nfi ON n.type = nfi.type_name
291 WHERE n.nid = %d AND nfi.field_name = '%s'";
292 $result = db_fetch_object(db_query(db_rewrite_sql($query), $item['nid'], $item['field_name']));
293 $items[$delta]['default field'] = $result->title . ':' . $result->label . ':' . $item['delta'] . ' [' . implode(':', $item) . ']';
294 }
295 }
296 break;
297
298 case 'form':
299 $form = array();
300 $form[$field['field_name']] = array('#tree' => TRUE);
301
302 if ($field['multiple']) {
303 $form[$field['field_name']]['#type'] = 'fieldset';
304 $form[$field['field_name']]['#description'] = t($field['widget']['description']);
305 $delta = 0;
306 foreach ($items as $item) {
307 if (!empty($item['nid']) && !empty($item['field_name']) && is_numeric($item['delta'])) {
308 $form[$field['field_name']][$delta]['field'] = array(
309 '#type' => 'textfield',
310 '#title' => ($delta == 0) ? t($field['widget']['label']) : '',
311 '#autocomplete_path' => 'fieldreference/autocomplete/'. $field['field_name'],
312 '#default_value' => $item['default field'],
313 '#required' => ($delta == 0) ? $field['required'] : FALSE,
314 );
315 $delta++;
316 }
317 }
318 foreach (range($delta, $delta + 2) as $delta) {
319 $form[$field['field_name']][$delta]['field'] = array(
320 '#type' => 'textfield',
321 '#title' => ($delta == 0) ? t($field['widget']['label']) : '',
322 '#autocomplete_path' => 'fieldreference/autocomplete/'. $field['field_name'],
323 '#default_value' => '',
324 '#required' => ($delta == 0) ? $field['required'] : FALSE,
325 );
326 }
327 }
328 else {
329 $form[$field['field_name']][0]['field'] = array(
330 '#type' => 'textfield',
331 '#title' => t($field['widget']['label']),
332 '#autocomplete_path' => 'fieldreference/autocomplete/'. $field['field_name'],
333 '#default_value' => $items[0]['default field'],
334 '#required' => $field['required'],
335 '#description' => t($field['widget']['description']),
336 );
337 }
338 return $form;
339
340 case 'validate':
341 foreach ($items as $delta => $item) {
342 $error_field = $field['field_name'] .']['. $delta .'][field';
343 if (!empty($item['field'])) {
344 preg_match('/^(?:\s*|(.*)\s*:\s*(?:.*)\s*:\s*(?:\d+)\s+)?\[\s*(\d+)\s*:\s*([a-zA-Z0-9_\-]+)\s*:\s*(\d+)\s*\]$/', $item['field'], $matches);
345 if (!empty($matches)) {
346 list(, $title, $nid, $field_name, $delta) = $matches;
347 if (!empty($title) && ($n = node_load($nid)) && $title != $n->title) {
348 form_set_error($error_field, t('%name : Title mismatch. Please check your selection.', array('%name' => t($field['widget']['label']))));
349 }
350 }
351 }
352 }
353 return;
354
355 case 'process form values':
356 $fields = array();
357 foreach ($items as $delta => $item) {
358 if (!empty($item['field'])) {
359 preg_match('/^(?:\s*|(.*)\s+)?\[\s*(\d+)\s*:\s*([a-zA-Z0-9_\-]+)\s*:\s*(\d+)\s*\]$/', $item['field'], $matches);
360 if (!empty($matches)) {
361 list(, , $referenced_field_nid, $referenced_field_name, $referenced_field_delta) = $matches;
362 $items[$delta]['nid'] = $referenced_field_nid;
363 $items[$delta]['field_name'] = $referenced_field_name;
364 $items[$delta]['delta'] = $referenced_field_delta;
365 $items[$delta]['error_field'] = $field['field_name'] .']['. $delta .'][field';
366 $fields[] = $referenced_field_nid . ':' . $referenced_field_name . ':' . $referenced_field_delta;
367 }
368 else {
369 // TODO :
370 // the best thing would be to present the user with an additional form,
371 // allowing the user to choose between valid candidates with the same title
372 // ATM, we pick the first matching candidate...
373 $refs = _fieldreference_potential_references($field, $item['field'], TRUE);
374 $referenced_field = (!empty($refs)) ? array_shift(array_values($refs)) : 0;
375 if (!empty($referenced_field)) {
376 $items[$delta]['nid'] = $referenced_field->nid;
377 $items[$delta]['field_name'] = $referenced_field->name;
378 $items[$delta]['delta'] = $referenced_field->delta;
379 $items[$delta]['error_field'] = $field['field_name'] .']['. $delta .'][field';
380 $fields[] = $referenced_field->nid . ':' . $referenced_field->name . ':' . $referenced_field->delta;
381 }
382 elseif ($delta > 0) {
383 unset($items[$delta]);
384 }
385 }
386 unset($items[$delta]['field']);
387 }
388 else {
389 unset($items[$delta]);
390 }
391 }
392 $items['fields'] = $fields;
393 break;
394 }
395 }
396 }
397
398 /**
399 * Fetch an array of all candidate referenced fields, for use in presenting the selection form to the user.
400 */
401 function _fieldreference_potential_references($field, $string = '', $exact_string = FALSE) {
402 if (isset($field['referenceable_types'])) {
403 $sub_queries = array();
404 $args = array();
405 $i = 0;
406
407 foreach($field['referenceable_types'] as $referenceable_type) {
408 if($referenceable_type) {
409 $referenceable_type = explode(':', $referenceable_type);
410 $type = $referenceable_type[0];
411 $field_name = $referenceable_type[1];
412
413 $referenced_field = content_fields($field_name);
414 $db_info = content_database_info($referenced_field);
415 $sub_query = "(SELECT n.nid AS nid, n.title AS title, n.type AS type, '%s' AS name, '%s' AS label, " . ($referenced_field['multiple'] ? "t$i.delta" : "'0'") . " AS delta FROM {node} n INNER JOIN {%s} t$i ON n.nid = t$i.nid WHERE n.type = '%s'";
416
417 $args[] = $field_name;
418 $args[] = $referenced_field['widget']['label'];
419 $args[] = $db_info['table'];
420 $args[] = $type;
421
422 if (!empty($string)) {
423 $sub_query .= $exact_string ? " AND n.title = '%s'" : " AND n.title LIKE '%%%s%'";
424 $args[] = $string;
425 }
426
427 $sub_query .= ')';
428
429 $sub_queries[] = $sub_query;
430 ++$i;
431 }
432 }
433
434 $query = implode(' UNION ', $sub_queries) . " ORDER BY title, type, label, delta";
435 $result = db_query(db_rewrite_sql($query), $args);
436
437 if (db_num_rows($result) == 0) {
438 return array();
439 }
440
441 $rows = array();
442 while ($row = db_fetch_object($result)) {
443 $rows[$row->nid . ':' . $row->name . ':' . $row->delta] = $row;
444 }
445
446 return $rows;
447 }
448
449 return array();
450 }
451
452 /**
453 * Retrieve a pipe delimited string of autocomplete suggestions
454 */
455 function fieldreference_autocomplete($field_name, $string = '') {
456 $field = content_fields($field_name);
457 $matches = array();
458
459 foreach (_fieldreference_potential_references($field, $string) as $row) {
460 $matches[$row->title . ':' . $row->label . ':' . $row->delta . ' [' . $row->nid . ':' . $row->name . ':' . $row->delta . ']'] = _fieldreference_item($field, $row);
461 }
462
463 print drupal_to_js($matches);
464 exit();
465 }
466
467 function _fieldreference_item($field, $item, $html = TRUE) {
468 $output = theme('fieldreference_item_simple', $item, $field['multiple']);
469 $output = $html ? check_plain($output) : $output;
470
471 return $output;
472 }
473
474 function theme_fieldreference_item_simple($item, $multiple = FALSE) {
475 $output = $item->title . ':' . $item->label;
476
477 if ($multiple) {
478 $output .= ':' . $item->delta;
479 }
480
481 return $output;
482 }
483
484 /**
485 * Provide a list of users to filter on.
486 */
487 function _fieldreference_filter_handler($op, $filterinfo) {
488 $options = array(0 => t('<empty>'));
489 $references = _fieldreference_potential_references($filterinfo['extra']['field']);
490 $options = $options + array_map(theme_fieldreference_item_simple, $references);
491 return $options;
492 }
493

  ViewVC Help
Powered by ViewVC 1.1.2