1 |
85ad3d82
|
Assos Assos
|
<?php
|
2 |
|
|
|
3 |
|
|
/**
|
4 |
|
|
* @file
|
5 |
|
|
* Provides an additional widget for term fields to create hierarchical selects.
|
6 |
|
|
*/
|
7 |
|
|
|
8 |
|
|
/**
|
9 |
|
|
* Implements hook_menu().
|
10 |
|
|
*/
|
11 |
|
|
function shs_menu() {
|
12 |
|
|
$items = array();
|
13 |
|
|
|
14 |
|
|
// Create menu item for JSON callbacks.
|
15 |
|
|
$items['js/shs'] = array(
|
16 |
|
|
'title' => 'JSON callback',
|
17 |
|
|
'description' => 'JSON callbacks for Simple hierarchical select',
|
18 |
|
|
'page callback' => 'shs_json',
|
19 |
|
|
'access callback' => 'user_access',
|
20 |
|
|
'access arguments' => array('access content'),
|
21 |
|
|
'type' => MENU_CALLBACK,
|
22 |
|
|
);
|
23 |
|
|
|
24 |
|
|
return $items;
|
25 |
|
|
}
|
26 |
|
|
|
27 |
|
|
/**
|
28 |
|
|
* Implements hook_js().
|
29 |
|
|
*/
|
30 |
|
|
function shs_js() {
|
31 |
|
|
return array(
|
32 |
|
|
'json' => array(
|
33 |
|
|
'callback' => 'shs_json',
|
34 |
|
|
'access callback' => 'user_access',
|
35 |
|
|
'access arguments' => array('access content'),
|
36 |
|
|
'dependencies' => array('taxonomy'),
|
37 |
|
|
),
|
38 |
|
|
);
|
39 |
|
|
}
|
40 |
|
|
|
41 |
|
|
/**
|
42 |
|
|
* Menu callback to get data in JSON format.
|
43 |
|
|
*/
|
44 |
|
|
function shs_json() {
|
45 |
|
|
$result = array(
|
46 |
|
|
'success' => FALSE,
|
47 |
|
|
'data' => array(),
|
48 |
|
|
);
|
49 |
|
|
if (isset($_POST['callback'])) {
|
50 |
|
|
// Get name of function we need to call to get the data.
|
51 |
|
|
$_callback = check_plain($_POST['callback']);
|
52 |
|
|
// Is this a valid callback?
|
53 |
|
|
$valid_callbacks = shs_json_callbacks();
|
54 |
|
|
if (isset($valid_callbacks[$_callback]) && !empty($valid_callbacks[$_callback]['callback']) && function_exists($valid_callbacks[$_callback]['callback'])) {
|
55 |
|
|
// Get arguments and validate them.
|
56 |
|
|
$post_args = (isset($_POST['arguments']) && is_array($_POST['arguments'])) ? $_POST['arguments'] : array();
|
57 |
|
|
$arguments = _shs_json_callback_get_arguments($valid_callbacks[$_callback], $post_args);
|
58 |
|
|
if (($callback_result = call_user_func_array($valid_callbacks[$_callback]['callback'], $arguments)) !== FALSE) {
|
59 |
|
|
$result['success'] = TRUE;
|
60 |
|
|
$result['data'] = $callback_result;
|
61 |
|
|
}
|
62 |
|
|
}
|
63 |
|
|
}
|
64 |
|
|
// Return result as JSON string.
|
65 |
|
|
drupal_json_output($result);
|
66 |
|
|
}
|
67 |
|
|
|
68 |
|
|
/**
|
69 |
|
|
* Get a list of supported JSON callbacks.
|
70 |
|
|
*
|
71 |
|
|
* @return <array>
|
72 |
|
|
* List of valid callbacks with the following structure:
|
73 |
|
|
* - [name of callback]
|
74 |
|
|
* - 'callback': function to call
|
75 |
|
|
* - 'arguments'
|
76 |
|
|
* - [name of argument]: [validation function] (FALSE for no validation)
|
77 |
|
|
*/
|
78 |
|
|
function shs_json_callbacks() {
|
79 |
|
|
$callbacks = array(
|
80 |
|
|
'shs_json_term_get_children' => array(
|
81 |
|
|
'callback' => 'shs_json_term_get_children',
|
82 |
|
|
'arguments' => array(
|
83 |
|
|
'vid' => 'is_numeric',
|
84 |
|
|
'parent' => 'is_array',
|
85 |
|
|
'settings' => 'is_array',
|
86 |
|
|
),
|
87 |
|
|
),
|
88 |
|
|
'shs_json_term_add' => array(
|
89 |
|
|
'callback' => 'shs_json_term_add',
|
90 |
|
|
'arguments' => array(
|
91 |
|
|
'vid' => 'is_numeric',
|
92 |
|
|
'parent' => 'is_numeric',
|
93 |
|
|
'name' => 'is_string',
|
94 |
|
|
),
|
95 |
|
|
),
|
96 |
|
|
);
|
97 |
|
|
// Let other modules add some more callbacks and alter the existing.
|
98 |
|
|
drupal_alter('shs_json_callbacks', $callbacks);
|
99 |
|
|
return $callbacks;
|
100 |
|
|
}
|
101 |
|
|
|
102 |
|
|
/**
|
103 |
|
|
* Helper function to get the (validated) arguments for a JSON callback.
|
104 |
|
|
*
|
105 |
|
|
* @param <array> $callback
|
106 |
|
|
* Callback definition from campus_events_json_callbacks().
|
107 |
|
|
* @param <array> $arguments
|
108 |
|
|
* Unfiltered arguments posted with $.ajax().
|
109 |
|
|
*
|
110 |
|
|
* @return <array>
|
111 |
|
|
* List of (validated) arguments for this callback. Any arguments not defined
|
112 |
|
|
* for this callback will be removed.
|
113 |
|
|
*/
|
114 |
|
|
function _shs_json_callback_get_arguments($callback, $arguments) {
|
115 |
|
|
$result = array();
|
116 |
|
|
// Get arguments from callback definition.
|
117 |
|
|
$callback_arguments = $callback['arguments'];
|
118 |
|
|
foreach ($arguments as $key => $value) {
|
119 |
|
|
if (isset($callback_arguments[$key])) {
|
120 |
|
|
$argument_valid = TRUE;
|
121 |
|
|
if ((($validation_function = $callback_arguments[$key]) !== FALSE) && function_exists($validation_function)) {
|
122 |
|
|
// Validate argument.
|
123 |
|
|
$argument_valid = $validation_function($value);
|
124 |
|
|
}
|
125 |
|
|
if ($argument_valid) {
|
126 |
|
|
// Add argument and its value to the result list.
|
127 |
|
|
$result[$key] = $value;
|
128 |
|
|
}
|
129 |
|
|
}
|
130 |
|
|
}
|
131 |
|
|
return $result;
|
132 |
|
|
}
|
133 |
|
|
|
134 |
|
|
/**
|
135 |
|
|
* Implements hook_views_data_alter().
|
136 |
|
|
*/
|
137 |
|
|
function shs_views_data_alter(&$data) {
|
138 |
|
|
// Get a list of all field instances with widget type "taxonomy_shs".
|
139 |
|
|
$instances = _shs_get_instances('node');
|
140 |
|
|
foreach ($instances as $field_instances) {
|
141 |
|
|
foreach ($field_instances as $field_name => $instance) {
|
142 |
|
|
// Replace filter handler for this field.
|
143 |
|
|
if (!empty($data["field_data_{$field_name}"]["{$field_name}_tid"]['filter']['handler'])) {
|
144 |
|
|
$data["field_data_{$field_name}"]["{$field_name}_tid"]['filter']['handler'] = 'shs_handler_filter_term_node_tid';
|
145 |
|
|
}
|
146 |
|
|
}
|
147 |
|
|
}
|
148 |
|
|
|
149 |
|
|
// Add filter handler for term ID with depth.
|
150 |
|
|
$data['node']['shs_term_node_tid_depth'] = array(
|
151 |
|
|
'help' => t('Display content if it has the selected taxonomy terms, or children of the selected terms. Due to additional complexity, this has fewer options than the versions without depth. Optionally the filter will use a simple hierarchical select for the selection of terms.'),
|
152 |
|
|
'real field' => 'nid',
|
153 |
|
|
'filter' => array(
|
154 |
|
|
'title' => t('Has taxonomy terms (with depth; %type)', array('%type' => 'Simple hierarchical select')),
|
155 |
|
|
'handler' => 'shs_handler_filter_term_node_tid_depth',
|
156 |
|
|
),
|
157 |
|
|
);
|
158 |
|
|
}
|
159 |
|
|
|
160 |
|
|
/**
|
161 |
|
|
* Implements hook_field_widget_info().
|
162 |
|
|
*/
|
163 |
|
|
function shs_field_widget_info() {
|
164 |
|
|
return array(
|
165 |
|
|
'taxonomy_shs' => array(
|
166 |
|
|
'label' => t('Simple hierarchical select'),
|
167 |
|
|
'field types' => array('taxonomy_term_reference'),
|
168 |
|
|
'settings' => array(
|
169 |
|
|
'shs' => array(
|
170 |
|
|
'node_count' => FALSE,
|
171 |
|
|
'create_new_terms' => FALSE,
|
172 |
|
|
'create_new_levels' => FALSE,
|
173 |
|
|
'force_deepest' => FALSE,
|
174 |
|
|
),
|
175 |
|
|
),
|
176 |
|
|
),
|
177 |
|
|
);
|
178 |
|
|
}
|
179 |
|
|
|
180 |
|
|
/**
|
181 |
|
|
* Implements hook__field_widget_settings_form().
|
182 |
|
|
*/
|
183 |
|
|
function shs_field_widget_settings_form($field, $instance) {
|
184 |
|
|
$widget = $instance['widget'];
|
185 |
|
|
$settings = $widget['settings'];
|
186 |
|
|
|
187 |
|
|
$form = array();
|
188 |
|
|
|
189 |
|
|
$form['shs'] = array(
|
190 |
|
|
'#type' => 'fieldset',
|
191 |
|
|
'#title' => 'Simple hierarchical select settings',
|
192 |
|
|
'#collapsible' => TRUE,
|
193 |
|
|
'#collapsed' => FALSE,
|
194 |
|
|
'#tree' => TRUE,
|
195 |
|
|
);
|
196 |
|
|
$form['shs']['node_count'] = array(
|
197 |
|
|
'#type' => 'checkbox',
|
198 |
|
|
'#title' => t('Display number of nodes'),
|
199 |
|
|
'#description' => t('Display the number of nodes associated with the term.'),
|
200 |
|
|
'#default_value' => empty($settings['shs']['node_count']) ? FALSE : $settings['shs']['node_count'],
|
201 |
|
|
);
|
202 |
|
|
$form['shs']['create_new_terms'] = array(
|
203 |
|
|
'#type' => 'checkbox',
|
204 |
|
|
'#title' => t('Allow creating new terms'),
|
205 |
|
|
'#description' => t('If checked the user will be able to create new terms (permission to edit terms in this vocabulary must be set).'),
|
206 |
|
|
'#default_value' => empty($settings['shs']['create_new_terms']) ? FALSE : $settings['shs']['create_new_terms'],
|
207 |
|
|
);
|
208 |
|
|
$form['shs']['create_new_levels'] = array(
|
209 |
|
|
'#type' => 'checkbox',
|
210 |
|
|
'#title' => t('Allow creating new levels'),
|
211 |
|
|
'#description' => t('If checked the user will be able to create new children for items which do not have any children yet (permission to edit terms in this vocabulary must be set).'),
|
212 |
|
|
'#default_value' => empty($settings['shs']['create_new_levels']) ? FALSE : $settings['shs']['create_new_levels'],
|
213 |
|
|
'#states' => array(
|
214 |
|
|
'visible' => array(
|
215 |
|
|
':input[name="instance[widget][settings][shs][create_new_terms]"]' => array('checked' => TRUE),
|
216 |
|
|
),
|
217 |
|
|
),
|
218 |
|
|
);
|
219 |
|
|
$form['shs']['force_deepest'] = array(
|
220 |
|
|
'#type' => 'checkbox',
|
221 |
|
|
'#title' => t('Force selection of deepest level'),
|
222 |
|
|
'#description' => t('If checked the user will be forced to select terms from the deepest level.'),
|
223 |
|
|
'#default_value' => empty($settings['shs']['force_deepest']) ? FALSE : $settings['shs']['force_deepest'],
|
224 |
|
|
);
|
225 |
|
|
|
226 |
|
|
// "Chosen" integration.
|
227 |
|
|
if (module_exists('chosen')) {
|
228 |
|
|
$form['shs']['use_chosen'] = array(
|
229 |
|
|
'#type' => 'select',
|
230 |
|
|
'#title' => t('Output this field with !chosen', array('!chosen' => l(t('Chosen'), 'http://drupal.org/project/chosen'))),
|
231 |
|
|
'#description' => t('Select in which cases the element will use the !chosen module for the term selection of each level.', array('!chosen' => l(t('Chosen'), 'http://drupal.org/project/chosen'))),
|
232 |
|
|
'#default_value' => empty($settings['shs']['use_chosen']) ? 'chosen' : $settings['shs']['use_chosen'],
|
233 |
|
|
'#options' => array(
|
234 |
|
|
'chosen' => t('let chosen decide'),
|
235 |
|
|
'always' => t('always'),
|
236 |
|
|
'never' => t('never'),
|
237 |
|
|
),
|
238 |
|
|
);
|
239 |
|
|
}
|
240 |
|
|
|
241 |
|
|
return $form;
|
242 |
|
|
}
|
243 |
|
|
|
244 |
|
|
/**
|
245 |
|
|
* Implements hook_field_widget_error().
|
246 |
|
|
*/
|
247 |
|
|
function shs_field_widget_error($element, $error, $form, &$form_state) {
|
248 |
|
|
form_error($element, $error['message']);
|
249 |
|
|
}
|
250 |
|
|
|
251 |
|
|
/**
|
252 |
|
|
* Implements hook_field_widget_form().
|
253 |
|
|
*/
|
254 |
|
|
function shs_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
|
255 |
|
|
// Get value.
|
256 |
|
|
$element_value = NULL;
|
257 |
|
|
if (!empty($items[$delta]['tid'])) {
|
258 |
|
|
// Use saved value from database or cache.
|
259 |
|
|
$element_value = $items[$delta]['tid'];
|
260 |
|
|
}
|
261 |
|
|
elseif (!empty($form_state['values'][$element['#field_name']][$element['#language']][$delta]['tid'])) {
|
262 |
|
|
// Use value from form_state (for example for fields with cardinality = -1).
|
263 |
|
|
$element_value = $form_state['values'][$element['#field_name']][$element['#language']][$delta]['tid'];
|
264 |
|
|
}
|
265 |
|
|
|
266 |
|
|
// Get vocabulary.
|
267 |
|
|
$allowed_values = reset($field['settings']['allowed_values']);
|
268 |
|
|
if (empty($allowed_values['vocabulary']) || ($vocabulary = taxonomy_vocabulary_machine_name_load($allowed_values['vocabulary'])) === FALSE) {
|
269 |
|
|
// No vocabulary selected yet or vocabulary not found.
|
270 |
|
|
return array();
|
271 |
|
|
}
|
272 |
|
|
|
273 |
|
|
// Check if term exists (may be deleted).
|
274 |
|
|
if ($element_value && (($term = taxonomy_term_load($element_value)) === FALSE)) {
|
275 |
|
|
$element_value = 0;
|
276 |
|
|
}
|
277 |
|
|
|
278 |
|
|
if (!user_access('edit terms in ' . $vocabulary->vid)) {
|
279 |
|
|
// Update setting based on permission.
|
280 |
|
|
$instance['widget']['settings']['shs']['create_new_terms'] = FALSE;
|
281 |
|
|
}
|
282 |
|
|
$instance['widget']['settings']['shs']['required'] = $element['#required'];
|
283 |
|
|
|
284 |
|
|
// Element is required and there is no initial value.
|
285 |
|
|
if (empty($element_value) && $element['#required']) {
|
286 |
|
|
// Load list of options.
|
287 |
|
|
$options = shs_term_get_children($vocabulary->vid, 0);
|
288 |
|
|
if (count($options)) {
|
289 |
|
|
// Set element value to first available option.
|
290 |
|
|
$option_keys = array_keys($options);
|
291 |
|
|
$element_value = reset($option_keys);
|
292 |
|
|
}
|
293 |
|
|
}
|
294 |
|
|
|
295 |
|
|
// Create element.
|
296 |
|
|
$element += array(
|
297 |
|
|
'#type' => 'textfield',
|
298 |
|
|
'#default_value' => empty($element_value) ? NULL : $element_value,
|
299 |
|
|
'#attributes' => array(
|
300 |
|
|
'class' => array('shs-enabled'),
|
301 |
|
|
),
|
302 |
|
|
// Prevent errors with drupal_strlen().
|
303 |
|
|
'#maxlength' => NULL,
|
304 |
|
|
'#element_validate' => array('shs_field_widget_validate'),
|
305 |
|
|
'#after_build' => array('shs_field_widget_afterbuild'),
|
306 |
|
|
'#shs_settings' => $instance['widget']['settings']['shs'],
|
307 |
|
|
'#shs_vocabulary' => $vocabulary,
|
308 |
|
|
);
|
309 |
|
|
|
310 |
|
|
return array('tid' => $element);
|
311 |
|
|
}
|
312 |
|
|
|
313 |
|
|
/**
|
314 |
|
|
* Afterbuild callback for widgets of type "taxonomy_shs".
|
315 |
|
|
*/
|
316 |
|
|
function shs_field_widget_afterbuild($element, &$form_state) {
|
317 |
|
|
$js_added = &drupal_static(__FUNCTION__ . '_js_added', array());
|
318 |
|
|
// Generate a random hash to avoid merging of settings by drupal_add_js.
|
319 |
|
|
// This is necessary until http://drupal.org/node/208611 lands for D7.
|
320 |
|
|
$js_hash = &drupal_static(__FUNCTION__ . '_js_hash');
|
321 |
|
|
|
322 |
|
|
if (empty($js_hash)) {
|
323 |
|
|
$js_hash = _shs_create_hash();
|
324 |
|
|
}
|
325 |
|
|
|
326 |
|
|
$parents = array();
|
327 |
|
|
// Get value from element.
|
328 |
|
|
if (!empty($form_state['values'][$element['#field_name']][$element['#language']][$element['#delta']]['tid'])) {
|
329 |
|
|
// Use value from form_state (for example for fields with cardinality = -1).
|
330 |
|
|
$element['#default_value'] = $form_state['values'][$element['#field_name']][$element['#language']][$element['#delta']];
|
331 |
|
|
}
|
332 |
|
|
|
333 |
|
|
// Add main Javascript behavior and style only once.
|
334 |
|
|
if (count($js_added) == 0) {
|
335 |
|
|
// Add behavior.
|
336 |
|
|
drupal_add_js(drupal_get_path('module', 'shs') . '/js/shs.js');
|
337 |
|
|
// Add styles.
|
338 |
|
|
drupal_add_css(drupal_get_path('module', 'shs') . '/theme/shs.form.css');
|
339 |
|
|
}
|
340 |
|
|
|
341 |
|
|
// Create Javascript settings for the element only if it hasn't been added
|
342 |
|
|
// before.
|
343 |
|
|
if (empty($js_added[$element['#name']][$js_hash])) {
|
344 |
|
|
$element_value = $element['#default_value']['tid'];
|
345 |
|
|
|
346 |
|
|
if (empty($element_value)) {
|
347 |
|
|
// Add fake parent for new items.
|
348 |
|
|
$parents[] = array('tid' => 0);
|
349 |
|
|
}
|
350 |
|
|
else {
|
351 |
|
|
$term_parents = taxonomy_get_parents_all($element_value);
|
352 |
|
|
foreach ($term_parents as $term) {
|
353 |
|
|
// Create term lineage.
|
354 |
|
|
$parents[] = array('tid' => $term->tid);
|
355 |
|
|
}
|
356 |
|
|
}
|
357 |
|
|
|
358 |
|
|
// Create settings needed for our js magic.
|
359 |
|
|
$settings_js = array(
|
360 |
|
|
'shs' => array(
|
361 |
|
|
"{$element['#name']}" => array(
|
362 |
|
|
$js_hash => array(
|
363 |
|
|
'vid' => $element['#shs_vocabulary']->vid,
|
364 |
|
|
'settings' => $element['#shs_settings'],
|
365 |
|
|
'default_value' => $element['#default_value'],
|
366 |
|
|
'parents' => array_reverse($parents),
|
367 |
|
|
),
|
368 |
|
|
),
|
369 |
|
|
),
|
370 |
|
|
);
|
371 |
|
|
|
372 |
|
|
// Add settings.
|
373 |
|
|
drupal_add_js($settings_js, 'setting');
|
374 |
|
|
|
375 |
|
|
if (empty($js_added[$element['#name']])) {
|
376 |
|
|
$js_added[$element['#name']] = array();
|
377 |
|
|
}
|
378 |
|
|
$js_added[$element['#name']][$js_hash] = TRUE;
|
379 |
|
|
}
|
380 |
|
|
|
381 |
|
|
return $element;
|
382 |
|
|
}
|
383 |
|
|
|
384 |
|
|
/**
|
385 |
|
|
* Validation handler for widgets of type "taxonomy_shs".
|
386 |
|
|
*/
|
387 |
|
|
function shs_field_widget_validate($element, &$form_state, $form) {
|
388 |
|
|
$field_name = $element['#field_name'];
|
389 |
|
|
$field_language = $element['#language'];
|
390 |
|
|
|
391 |
|
|
if (empty($form_state['field'][$field_name][$field_language]['instance']['widget'])) {
|
392 |
|
|
return;
|
393 |
|
|
}
|
394 |
|
|
$field = $form_state['field'][$field_name][$field_language];
|
395 |
|
|
$instance = $field['instance'];
|
396 |
|
|
$settings = empty($instance['widget']['settings']['shs']) ? array() : $instance['widget']['settings']['shs'];
|
397 |
|
|
|
398 |
|
|
// Do we want to force the user to select terms from the deepest level?
|
399 |
|
|
$force_deepest_level = empty($settings['force_deepest']) ? FALSE : $settings['force_deepest'];
|
400 |
|
|
$value = empty($element['#value']) ? 0 : $element['#value'];
|
401 |
|
|
if ($force_deepest_level && $value) {
|
402 |
|
|
// Get vocabulary.
|
403 |
|
|
$allowed_values = reset($field['field']['settings']['allowed_values']);
|
404 |
|
|
if (empty($allowed_values['vocabulary']) || ($vocabulary = taxonomy_vocabulary_machine_name_load($allowed_values['vocabulary'])) === FALSE) {
|
405 |
|
|
// No vocabulary selected yet or vocabulary not found.
|
406 |
|
|
form_error($element, t('Vocabulary %machine_name is configured as source for field %field_name but could not be found.', array('%machine_name' => $allowed_values['vocabulary'], '%field_name' => $field_name)));
|
407 |
|
|
}
|
408 |
|
|
// Does the selected term has any children?
|
409 |
|
|
$children = shs_term_get_children($vocabulary->vid, $value);
|
410 |
|
|
if (count($children)) {
|
411 |
|
|
form_error($element, t('You need to select a term from the deepest level.'));
|
412 |
|
|
}
|
413 |
|
|
}
|
414 |
|
|
}
|
415 |
|
|
|
416 |
|
|
/**
|
417 |
|
|
* Implements hook_field_formatter_info().
|
418 |
|
|
*/
|
419 |
|
|
function shs_field_formatter_info() {
|
420 |
|
|
return array(
|
421 |
|
|
'shs_default' => array(
|
422 |
|
|
'label' => t('Simple hierarchy'),
|
423 |
|
|
'field types' => array('taxonomy_term_reference'),
|
424 |
|
|
'settings' => array(
|
425 |
|
|
'linked' => FALSE,
|
426 |
|
|
),
|
427 |
|
|
),
|
428 |
|
|
);
|
429 |
|
|
}
|
430 |
|
|
|
431 |
|
|
/**
|
432 |
|
|
* Implements hook_field_formatter_settings_form().
|
433 |
|
|
*/
|
434 |
|
|
function shs_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
|
435 |
|
|
$display = $instance['display'][$view_mode];
|
436 |
|
|
$settings = $display['settings'];
|
437 |
|
|
|
438 |
|
|
$element = array();
|
439 |
|
|
|
440 |
|
|
if ($display['type'] == 'shs_default') {
|
441 |
|
|
$element['linked'] = array(
|
442 |
|
|
'#title' => t('Link to term page'),
|
443 |
|
|
'#type' => 'checkbox',
|
444 |
|
|
'#default_value' => $settings['linked'],
|
445 |
|
|
);
|
446 |
|
|
}
|
447 |
|
|
|
448 |
|
|
return $element;
|
449 |
|
|
}
|
450 |
|
|
|
451 |
|
|
/**
|
452 |
|
|
* Implements hook_field_formatter_settings_summary().
|
453 |
|
|
*/
|
454 |
|
|
function shs_field_formatter_settings_summary($field, $instance, $view_mode) {
|
455 |
|
|
$display = $instance['display'][$view_mode];
|
456 |
|
|
$settings = $display['settings'];
|
457 |
|
|
|
458 |
|
|
$summary = '';
|
459 |
|
|
|
460 |
|
|
if ($display['type'] == 'shs_default') {
|
461 |
|
|
$summary = t('Linked to term page: !linked', array('!linked' => $settings['linked'] ? t('Yes') : t('No')));
|
462 |
|
|
}
|
463 |
|
|
|
464 |
|
|
return $summary;
|
465 |
|
|
}
|
466 |
|
|
|
467 |
|
|
/**
|
468 |
|
|
* Implements hook_field_formatter_prepare_view().
|
469 |
|
|
*/
|
470 |
|
|
function shs_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
|
471 |
|
|
foreach ($entities as $entity_id => $entity) {
|
472 |
|
|
foreach ($items[$entity_id] as $delta => $item) {
|
473 |
|
|
$items[$entity_id][$delta]['parents'] = array();
|
474 |
|
|
// Load list of parent terms.
|
475 |
|
|
$parents = taxonomy_get_parents_all($item['tid']);
|
476 |
|
|
// Remove current term from list.
|
477 |
|
|
array_shift($parents);
|
478 |
|
|
foreach (array_reverse($parents) as $parent) {
|
479 |
|
|
$items[$entity_id][$delta]['parents'][$parent->tid] = $parent;
|
480 |
|
|
}
|
481 |
|
|
// Load term.
|
482 |
|
|
$items[$entity_id][$delta]['term'] = taxonomy_term_load($item['tid']);
|
483 |
|
|
}
|
484 |
|
|
}
|
485 |
|
|
}
|
486 |
|
|
|
487 |
|
|
/**
|
488 |
|
|
* Implements hook_field_formatter_view().
|
489 |
|
|
*/
|
490 |
|
|
function shs_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
|
491 |
|
|
$elements = array();
|
492 |
|
|
$settings = $display['settings'];
|
493 |
|
|
|
494 |
|
|
switch ($display['type']) {
|
495 |
|
|
case 'shs_default':
|
496 |
|
|
foreach ($items as $delta => $item) {
|
497 |
|
|
if (empty($item['tid'])) {
|
498 |
|
|
continue;
|
499 |
|
|
}
|
500 |
|
|
$list_items = array();
|
501 |
|
|
// Add parent term names.
|
502 |
|
|
foreach ($item['parents'] as $parent) {
|
503 |
|
|
$list_items[] = array(
|
504 |
|
|
'data' => $settings['linked'] ? l($parent->name, "taxonomy/term/{$parent->tid}") : $parent->name,
|
505 |
|
|
'class' => array('shs-parent'),
|
506 |
|
|
);
|
507 |
|
|
};
|
508 |
|
|
// Add name of selected term.
|
509 |
|
|
$list_items[] = array(
|
510 |
|
|
'data' => $settings['linked'] ? l($item['term']->name, "taxonomy/term/{$item['term']->tid}") : $item['term']->name,
|
511 |
|
|
'class' => array('shs-term-selected'),
|
512 |
|
|
);
|
513 |
|
|
$elements[$delta] = array(
|
514 |
|
|
'#items' => $list_items,
|
515 |
|
|
'#theme' => 'item_list',
|
516 |
|
|
'#attributes' => array(
|
517 |
|
|
'class' => 'shs-hierarchy',
|
518 |
|
|
),
|
519 |
|
|
);
|
520 |
|
|
}
|
521 |
|
|
|
522 |
|
|
// Add basic style.
|
523 |
|
|
$elements['#attached']['css'][] = drupal_get_path('module', 'shs') . '/theme/shs.formatter.css';
|
524 |
|
|
break;
|
525 |
|
|
}
|
526 |
|
|
|
527 |
|
|
return $elements;
|
528 |
|
|
}
|
529 |
|
|
|
530 |
|
|
/**
|
531 |
|
|
* Function to get the list of children of a term.
|
532 |
|
|
*
|
533 |
|
|
* The structure is stored in the database cache as well as in drupal_static().
|
534 |
|
|
* Cache has the following structure:
|
535 |
|
|
* <code>
|
536 |
|
|
* [$parent] => array(
|
537 |
|
|
* [0] => array(tid1, tid2, tid3), // !$node_count.
|
538 |
|
|
* [1] => array('tid1 (x)', 'tid2 (x)', 'tid3 (x)'), // $node_count.
|
539 |
|
|
* ),
|
540 |
|
|
* </code>
|
541 |
|
|
*
|
542 |
|
|
* @param <int> $vid
|
543 |
|
|
* ID of vocabulary the term is associated to.
|
544 |
|
|
* @param <int> $parent
|
545 |
|
|
* ID of parent term.
|
546 |
|
|
* @param <array> $settings
|
547 |
|
|
* Additional settings (for example "display node count").
|
548 |
|
|
* @param <boolean> $reset
|
549 |
|
|
* If TRUE, rebuild the cache for the given $vid and $parent.
|
550 |
|
|
*
|
551 |
|
|
* @return <array>
|
552 |
|
|
* List of child terms keyed by term id.
|
553 |
|
|
*/
|
554 |
|
|
function shs_term_get_children($vid, $parent = 0, $settings = array(), $reset = FALSE) {
|
555 |
|
|
$terms = &drupal_static(__FUNCTION__, array());
|
556 |
|
|
$node_count = !empty($settings['node_count']) && variable_get('taxonomy_maintain_index_table', TRUE);
|
557 |
|
|
|
558 |
|
|
if ($reset || ($vid && empty($terms[$vid][$parent][$node_count]))) {
|
559 |
|
|
// Initialize list.
|
560 |
|
|
$terms[$vid][$parent] = array(
|
561 |
|
|
0 => array(),
|
562 |
|
|
1 => array(),
|
563 |
|
|
);
|
564 |
|
|
$cache_key = "shs:{$vid}";
|
565 |
|
|
// Get cached values.
|
566 |
|
|
$cache = cache_get($cache_key);
|
567 |
|
|
if ($reset || !$cache || ($cache->expire && time() > $cache->expire) || empty($cache->data[$parent][$node_count])) {
|
568 |
|
|
// Cache is empty or data has become outdated or the parent is not cached.
|
569 |
|
|
if ($cache) {
|
570 |
|
|
// Cache exists and is not yet expired but $parent is missing.
|
571 |
|
|
$terms[$vid] = $cache->data;
|
572 |
|
|
}
|
573 |
|
|
if ($reset) {
|
574 |
|
|
$terms[$vid][$parent] = array(
|
575 |
|
|
0 => array(),
|
576 |
|
|
1 => array(),
|
577 |
|
|
);
|
578 |
|
|
}
|
579 |
|
|
// Get term children (only first level).
|
580 |
|
|
$tree = taxonomy_get_tree($vid, $parent, 1);
|
581 |
|
|
foreach ($tree as $term) {
|
582 |
|
|
$terms[$vid][$parent][0][$term->tid] = $term->name;
|
583 |
|
|
if ($node_count) {
|
584 |
|
|
// Count nodes associated to this term (and its children).
|
585 |
|
|
$num_nodes = _shs_term_get_node_count($term, TRUE);
|
586 |
|
|
// Update term label.
|
587 |
|
|
$terms[$vid][$parent][1][$term->tid] = t('!term_name (!node_count)', array('!term_name' => $term->name, '!node_count' => $num_nodes));
|
588 |
|
|
}
|
589 |
|
|
}
|
590 |
|
|
// Set cached data.
|
591 |
|
|
cache_set($cache_key, $terms[$vid], 'cache', CACHE_PERMANENT);
|
592 |
|
|
}
|
593 |
|
|
else {
|
594 |
|
|
// Use cached data.
|
595 |
|
|
$terms[$vid] = $cache->data;
|
596 |
|
|
}
|
597 |
|
|
}
|
598 |
|
|
// Allow other module to modify the list of terms.
|
599 |
|
|
$alter_options = array(
|
600 |
|
|
'vid' => $vid,
|
601 |
|
|
'parent' => $parent,
|
602 |
|
|
'settings' => $settings,
|
603 |
|
|
);
|
604 |
|
|
drupal_alter('shs_term_get_children', $terms, $alter_options);
|
605 |
|
|
|
606 |
|
|
return empty($terms[$vid][$parent][$node_count]) ? array() : $terms[$vid][$parent][$node_count];
|
607 |
|
|
}
|
608 |
|
|
|
609 |
|
|
/**
|
610 |
|
|
* JSON callback to get the list of children of a term.
|
611 |
|
|
*
|
612 |
|
|
* @param <int> $vid
|
613 |
|
|
* ID of vocabulary the term is associated to.
|
614 |
|
|
* @param <int> $parent
|
615 |
|
|
* ID of parent term.
|
616 |
|
|
* @param <array> $settings
|
617 |
|
|
* Additional settings (for example "display node count").
|
618 |
|
|
*
|
619 |
|
|
* @return <array>
|
620 |
|
|
* Associative list of child terms.
|
621 |
|
|
*
|
622 |
|
|
* @see shs_term_get_children()
|
623 |
|
|
*/
|
624 |
|
|
function shs_json_term_get_children($vid, $parent = array(), $settings = array()) {
|
625 |
|
|
$scope = $result = array();
|
626 |
|
|
foreach ($parent as $tid) {
|
627 |
|
|
$scope[] = shs_term_get_children($vid, $tid, $settings);
|
628 |
|
|
}
|
629 |
|
|
|
630 |
|
|
// Rewrite result set to preserve original sort of terms through JSON request.
|
631 |
|
|
foreach ($scope as $terms) {
|
632 |
|
|
foreach ($terms as $tid => $label) {
|
633 |
|
|
$result[] = array(
|
634 |
|
|
'tid' => $tid,
|
635 |
|
|
'label' => $label,
|
636 |
|
|
);
|
637 |
|
|
}
|
638 |
|
|
}
|
639 |
|
|
|
640 |
|
|
return $result;
|
641 |
|
|
}
|
642 |
|
|
/**
|
643 |
|
|
* Adds a term with ajax.
|
644 |
|
|
*
|
645 |
|
|
* @param <int> $vid
|
646 |
|
|
* ID of vocabulary to create the term in.
|
647 |
|
|
* @param <int> $parent
|
648 |
|
|
* ID of parent term (0 for top level).
|
649 |
|
|
* @param <string> $term_name
|
650 |
|
|
* Name of new term.
|
651 |
|
|
*
|
652 |
|
|
* @return <mixed>
|
653 |
|
|
* Array with tid and name or FALSE on error.
|
654 |
|
|
*/
|
655 |
|
|
function shs_json_term_add($vid, $parent, $term_name) {
|
656 |
|
|
if (!user_access('edit terms in ' . $vid)) {
|
657 |
|
|
// Sorry, but this user may not add a term to this vocabulary.
|
658 |
|
|
return FALSE;
|
659 |
|
|
}
|
660 |
|
|
|
661 |
|
|
$term = (object) array(
|
662 |
|
|
'vid' => $vid,
|
663 |
|
|
'parent' => $parent,
|
664 |
|
|
'name' => check_plain(filter_xss($term_name)),
|
665 |
|
|
);
|
666 |
|
|
// Save term.
|
667 |
|
|
$status = taxonomy_term_save($term);
|
668 |
|
|
|
669 |
|
|
// Return term object or FALSE (in case of errors).
|
670 |
|
|
return ($status == SAVED_NEW) ? array('tid' => $term->tid, 'name' => $term->name) : FALSE;
|
671 |
|
|
}
|
672 |
|
|
|
673 |
|
|
/**
|
674 |
|
|
* Implements hook_hook_taxonomy_term_insert().
|
675 |
|
|
*/
|
676 |
|
|
function shs_taxonomy_term_insert($term) {
|
677 |
|
|
// Update vocabulary cache for the terms parents.
|
678 |
|
|
foreach ($term->parent as $parent) {
|
679 |
|
|
shs_term_get_children($term->vid, $parent, array('node_count' => TRUE), TRUE);
|
680 |
|
|
}
|
681 |
|
|
}
|
682 |
|
|
|
683 |
|
|
/**
|
684 |
|
|
* Implements hook_hook_taxonomy_term_update().
|
685 |
|
|
*/
|
686 |
|
|
function shs_taxonomy_term_update($term) {
|
687 |
|
|
// Update vocabulary cache for the terms parents.
|
688 |
|
|
foreach ($term->parent as $parent) {
|
689 |
|
|
shs_term_get_children($term->vid, $parent, array('node_count' => TRUE), TRUE);
|
690 |
|
|
}
|
691 |
|
|
}
|
692 |
|
|
|
693 |
|
|
/**
|
694 |
|
|
* Implements hook_form_FORM_ID_alter().
|
695 |
|
|
*/
|
696 |
|
|
function shs_form_taxonomy_overview_terms_alter(&$form, &$form_state, $form_id) {
|
697 |
|
|
$form['#submit'][] = 'shs_form_taxonomy_overview_terms_submit';
|
698 |
|
|
}
|
699 |
|
|
|
700 |
|
|
/**
|
701 |
|
|
* Implements hook_hook_taxonomy_term_delete().
|
702 |
|
|
*/
|
703 |
|
|
function shs_form_taxonomy_overview_terms_submit(&$form, &$form_state) {
|
704 |
|
|
// Update vocabulary cache for the terms parents.
|
705 |
|
|
shs_term_get_children($form_state['complete form']['#vocabulary']->vid, 0, array('node_count' => TRUE), TRUE);
|
706 |
|
|
}
|
707 |
|
|
|
708 |
|
|
/**
|
709 |
|
|
* Implements hook_form_FORM_ID_alter().
|
710 |
|
|
*/
|
711 |
|
|
function shs_form_taxonomy_form_term_alter(&$form, &$form_state, $form_id) {
|
712 |
|
|
if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
|
713 |
|
|
// Add custom submit handler to update cache.
|
714 |
|
|
array_unshift($form['#submit'], 'shs_form_taxonomy_form_term_submit');
|
715 |
|
|
}
|
716 |
|
|
}
|
717 |
|
|
|
718 |
|
|
/**
|
719 |
|
|
* Implements hook_hook_taxonomy_term_delete().
|
720 |
|
|
*/
|
721 |
|
|
function shs_form_taxonomy_form_term_submit(&$form, &$form_state) {
|
722 |
|
|
// Update vocabulary cache for the terms parents.
|
723 |
|
|
$parents = db_select('taxonomy_term_hierarchy', 'tth')
|
724 |
|
|
->fields('tth', array('parent'))
|
725 |
|
|
->condition('tid', $form_state['term']->tid)
|
726 |
|
|
->execute()
|
727 |
|
|
->fetchAll();
|
728 |
|
|
if ($parents) {
|
729 |
|
|
// Update vocabulary cache for the terms parents.
|
730 |
|
|
foreach ($parents as $parent) {
|
731 |
|
|
shs_term_get_children($form_state['term']->vid, $parent->parent, array('node_count' => TRUE), TRUE);
|
732 |
|
|
}
|
733 |
|
|
}
|
734 |
|
|
}
|
735 |
|
|
|
736 |
|
|
/**
|
737 |
|
|
* Helper function to get all instances of widgets with type "taxonomy_shs".
|
738 |
|
|
*
|
739 |
|
|
* @param <string> $entity_type
|
740 |
|
|
* Name of entity type.
|
741 |
|
|
* @param <string> $bundle
|
742 |
|
|
* Name of bundle (optional).
|
743 |
|
|
*
|
744 |
|
|
* @return <array>
|
745 |
|
|
* List of instances keyed by field name.
|
746 |
|
|
*/
|
747 |
|
|
function _shs_get_instances($entity_type, $bundle = NULL) {
|
748 |
|
|
$instances = array();
|
749 |
|
|
$field_instances = field_info_instances($entity_type, $bundle);
|
750 |
|
|
// Get all field instances with widget type "shs_taxonomy".
|
751 |
|
|
if (empty($bundle)) {
|
752 |
|
|
foreach ($field_instances as $bundle_name => $bundle_instances) {
|
753 |
|
|
foreach ($bundle_instances as $instance) {
|
754 |
|
|
if ($instance['widget']['type'] == 'taxonomy_shs') {
|
755 |
|
|
$instances[$bundle_name][$instance['field_name']] = $instance;
|
756 |
|
|
}
|
757 |
|
|
}
|
758 |
|
|
}
|
759 |
|
|
}
|
760 |
|
|
else {
|
761 |
|
|
foreach ($field_instances as $instance) {
|
762 |
|
|
if ($instance['widget']['type'] == 'taxonomy_shs') {
|
763 |
|
|
$instances[$instance['field_name']] = $instance;
|
764 |
|
|
}
|
765 |
|
|
}
|
766 |
|
|
}
|
767 |
|
|
return $instances;
|
768 |
|
|
}
|
769 |
|
|
|
770 |
|
|
/**
|
771 |
|
|
* Helper function to count number of nodes associated to a term.
|
772 |
|
|
*
|
773 |
|
|
* @param <object> $term
|
774 |
|
|
* The term object.
|
775 |
|
|
* @param <boolean> $count_children
|
776 |
|
|
* If set to TRUE, nodes in child terms are counted also.
|
777 |
|
|
*
|
778 |
|
|
* @return <int>
|
779 |
|
|
* Number of nodes within the term.
|
780 |
|
|
*/
|
781 |
|
|
function _shs_term_get_node_count($term, $count_children = FALSE) {
|
782 |
|
|
$num_nodes = &drupal_static(__FUNCTION__, array());
|
783 |
|
|
|
784 |
|
|
// Maybe this needs some more caching and value-updates on node_save()/
|
785 |
|
|
// _update()/delete().
|
786 |
|
|
if (empty($num_nodes["{$term->tid}:{$count_children}"])) {
|
787 |
|
|
// Count nodes associated to this term.
|
788 |
|
|
$num_nodes["{$term->tid}:{$count_children}"] = db_select('taxonomy_index', 'ti')
|
789 |
|
|
->fields('ti')
|
790 |
|
|
->condition('tid', $term->tid)
|
791 |
|
|
->execute()
|
792 |
|
|
->rowCount();
|
793 |
|
|
|
794 |
|
|
if ($count_children) {
|
795 |
|
|
$tids = array();
|
796 |
|
|
$tree = taxonomy_get_tree($term->vid, $term->tid);
|
797 |
|
|
foreach ($tree as $child_term) {
|
798 |
|
|
$tids[] = $child_term->tid;
|
799 |
|
|
}
|
800 |
|
|
if (count($tids)) {
|
801 |
|
|
$num_nodes["{$term->tid}:{$count_children}"] += db_select('taxonomy_index', 'ti')
|
802 |
|
|
->fields('ti')
|
803 |
|
|
->condition('tid', $tids, 'IN')
|
804 |
|
|
->execute()
|
805 |
|
|
->rowCount();
|
806 |
|
|
}
|
807 |
|
|
}
|
808 |
|
|
}
|
809 |
|
|
|
810 |
|
|
return isset($num_nodes["{$term->tid}:{$count_children}"]) ? $num_nodes["{$term->tid}:{$count_children}"] : 0;
|
811 |
|
|
}
|
812 |
|
|
|
813 |
|
|
/**
|
814 |
|
|
* Helper function to create a pseudo hash needed for javascript settings.
|
815 |
|
|
*
|
816 |
|
|
* @param <int> $length
|
817 |
|
|
* Lenght of string to return.
|
818 |
|
|
*
|
819 |
|
|
* @return <string>
|
820 |
|
|
* Random string.
|
821 |
|
|
*
|
822 |
|
|
* @see DrupalTestCase::randomName()
|
823 |
|
|
*/
|
824 |
|
|
function _shs_create_hash($length = 8) {
|
825 |
|
|
$values = array_merge(range(65, 90), range(97, 122), range(48, 57));
|
826 |
|
|
$max = count($values) - 1;
|
827 |
|
|
$hash = chr(mt_rand(97, 122));
|
828 |
|
|
for ($i = 1; $i < $length; $i++) {
|
829 |
|
|
$hash .= chr($values[mt_rand(0, $max)]);
|
830 |
|
|
}
|
831 |
|
|
return $hash;
|
832 |
|
|
} |