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

Contents of /contributions/modules/relevant_content/relevant_content.module

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


Revision 1.10 - (show annotations) (download) (as text)
Thu Oct 29 14:31:10 2009 UTC (4 weeks, 1 day ago) by njt1982
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-7--1
Changes since 1.9: +2 -2 lines
File MIME type: text/x-php
A Final tweak to sort out inline labels with the custom field template.
1 <?php
2 // $Id: relevant_content.module,v 1.9 2009/10/29 14:16:42 njt1982 Exp $
3
4 /**
5 * @file
6 * This module provides blocks to inform users of relevant content. This is done on a per-content-type basis
7 */
8
9
10 /**
11 * Implement hook_help().
12 */
13 function relevant_content_help($path, $arg) {
14 switch ($path) {
15 case 'admin/help' :
16 return t('Provides a block using the Views module to display relevant nodes for the current page.');
17 case 'admin/blocks/relevant_content' :
18 $output = '<p>'. t('On this page you can configure which blocks should be provided on a per-content-type basis. If you enabled a content type, please make sure to provided a block title.') .'</p>';
19 $output .= '<p>'. t('The <em>Limit</em> field allows you to provide a maximum number of nodes to be displayed for that block.') .'</p>';
20 $output .= '<p>'. t('The <em>Block Header Text</em> field allows you to provide some text which can appear at the top of the block - good for explaining to the user what the block is.') .'</p>';
21 return $output;
22 }
23 }
24
25
26 /**
27 * Implement hook_perm().
28 */
29 function relevant_content_perm() {
30 return array('administer relevant content');
31 }
32
33
34 /**
35 * Implement hook_menu().
36 */
37 function relevant_content_menu() {
38 $items = array();
39
40 $defaults = array(
41 'access arguments' => array('administer relevant content'),
42 'file' => 'relevant_content.admin.inc',
43 'file path' => drupal_get_path('module', 'relevant_content'),
44 );
45
46 $items['admin/config/relevant_content'] = array(
47 'title' => 'Relevant Content',
48 'description' => 'Configuration for Relevant Content',
49 'page callback' => 'system_admin_menu_block_page',
50 'file' => 'system.admin.inc',
51 'file path' => drupal_get_path('module', 'system'),
52 'access arguments' => array('administer relevant content'),
53 );
54
55 $items['admin/config/relevant_content/blocks'] = $defaults + array(
56 'title' => 'Relevant Content',
57 'description' => 'Configure the sites <em>relevant content</em> blocks.',
58 'page callback' => 'relevant_content_admin',
59 );
60
61 $items['admin/config/relevant_content/blocks/%relevant_content_delta'] = $defaults + array(
62 'title' => 'Edit Block',
63 'description' => 'Configure the sites <em>relevant content</em> blocks.',
64 'page callback' => 'drupal_get_form',
65 'page arguments' => array('relevant_content_admin_block_form', 4),
66 'type' => MENU_CALLBACK,
67 );
68
69 $items['admin/config/relevant_content/blocks/%relevant_content_delta/delete'] = $defaults + array(
70 'page callback' => 'relevant_content_admin_delete',
71 'page arguments' => array(4),
72 'type' => MENU_CALLBACK,
73 );
74
75 return $items;
76 }
77
78
79 /**
80 * Implement hook_load().
81 *
82 * This is simply here (currently) to act as a nice URL delta parser. Returns false if block not available.
83 * TODO: Modify system to load the settings. This means modifying form inputs too.
84 */
85 function relevant_content_delta_load($delta) {
86 $settings = variable_get('relevant_content', array());
87 if (isset($settings[$delta])) return $delta;
88
89 return FALSE;
90 }
91
92
93 /**
94 * Implement hook_form_alter().
95 */
96 function relevant_content_form_alter(&$form, &$form_state, $form_id) {
97 // If we're on a field edit form and the fiel type is relevant content, tweak the form to remove unecessary elements
98 if ($form_id == 'field_ui_field_edit_form' && $form['#field']['type'] == 'relevant_content') {
99 unset($form['instance']['default_value_widget']['#type']);
100 $form['field']['cardinality']['#type'] = $form['instance']['required']['#type'] = 'value';
101 }
102 }
103
104
105 /**
106 * Implement hook_block_info().
107 */
108 function relevant_content_block_info() {
109 $blocks = array();
110 $settings = variable_get('relevant_content', array());
111
112 if (!empty($settings)) {
113 foreach ($settings as $delta => $params) {
114 $blocks[$delta] = array(
115 'info' => t('Relevant Content: @title', array('@title' => $delta)),
116 'cache' => DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE,
117 'visibility' => 1,
118 'pages' => 'node/*',
119 );
120 }
121 }
122
123 return $blocks;
124 }
125
126
127 /**
128 * Implement hook_block_view().
129 */
130 function relevant_content_block_view($delta = '') {
131 $settings = variable_get('relevant_content', array());
132
133 //Get the terms for the current page using a little reusable wrapper function
134 $terms = relevant_content_get_page_terms();
135
136 //If there are no terms, not a lot of point in continuing
137 if (empty($terms)) {
138 return;
139 }
140
141 //Filter out the terms which are not in a selected vocabulary
142 foreach ($terms as $key => $term) {
143 if (isset($settings[$delta]['vocabs'][$term->vid])) {
144 $terms[$key] = $term->tid;
145 }
146 else {
147 unset($terms[$key]);
148 }
149 }
150
151 //Again - if there are no terms, no need to continue!
152 if (empty($terms)) {
153 return;
154 }
155
156 //Create a node exclusion list - this will exclude the currently viewed node - if applicable.
157 //This stops the currently viewed node appearing top of a list - afterall, it IS the most relevant!
158 $exclude = array();
159 if (arg(0) == 'node' && is_numeric(arg(1))) {
160 $exclude[] = arg(1);
161 }
162
163 if ($nodes = relevant_content_get_nodes($settings[$delta]['types'], $terms, $exclude, $settings[$delta]['limit'])) {
164 $header = isset($settings[$delta]['header_text']) ? $settings[$delta]['header_text'] : FALSE;
165 return array(
166 'subject' => t('Relevant Content'),
167 'content' => theme('relevant_content_block', array('nodes' => $nodes, 'header' => $header, 'delta' => $delta)),
168 );
169 }
170 }
171
172
173 /**
174 * Handy wrapper function to find the terms for the current page
175 */
176 function relevant_content_get_page_terms($node = NULL) {
177 /**
178 * If we have passed in a node, or if there is one available from the menu object, use the node ID to load all the terms for the node...
179 * This seems to be necessary due to the new Field API not really giving us "one" taxonomy array on the node.
180 */
181 if (isset($node) || (arg(0) == 'node' && is_numeric(arg(1)) && $node = menu_get_object())) {
182 // Select from the taxonomy_index and the taxonomy_term_data
183 $query = db_select('taxonomy_index', 't');
184 $query->join('taxonomy_term_data', 'td', 'td.tid = t.tid');
185
186 // We need the Term ID, Name and Vocabulary ID
187 $query->addField('t', 'tid');
188 $query->fields('td', array('vid', 'name'));
189
190 // And we need to filter for the node in question
191 $query->condition('t.nid', $node->nid);
192
193 // Get the terms as a tid-indexed array of objects
194 $terms = $query->execute()->fetchAllAssoc('tid');
195 }
196 // Fall back to the term_cache if the above worked
197 else {
198 $terms = relevant_content_term_cache();
199 }
200
201 // Provide a hook_relevant_content_terms where other modules can change or add to the relevant terms...
202 drupal_alter('relevant_content_terms', $terms);
203
204 return $terms;
205 }
206
207
208 /**
209 * Implement hook_theme().
210 */
211 function relevant_content_theme($existing, $type, $theme, $path) {
212 $base = array('file' => 'relevant_content.theme.inc', 'theme path' => $path);
213
214 return array(
215 'relevant_content_block' => $base + array('arguments' => array('nodes' => NULL, 'header' => NULL, 'delta' => NULL),),
216 'relevant_content_token_help' => $base + array('arguments' => array()),
217 'relevant_content_field_formatter_general' => $base + array('arguments' => array('element' => NULL, 'type' => NULL)),
218 'relevant_content_individual_field' => $base + array('arguments' => array('attributes' => NULL, 'result' => NULL)),
219 'field_formatter_relevant_content_default' => $base + array('arguments' => array('element' => NULL)),
220 'field_formatter_relevant_content_plain' => $base + array('arguments' => array('element' => NULL)),
221 'field_formatter_relevant_content_teaser' => $base + array('arguments' => array('element' => NULL)),
222 'field_formatter_relevant_content_full' => $base + array('arguments' => array('element' => NULL)),
223 'field_formatter_relevant_content_token_teaser' => $base + array('arguments' => array('element' => NULL)),
224 'field_formatter_relevant_content_token_full' => $base + array('arguments' => array('element' => NULL)),
225 );
226 }
227
228
229 /**
230 * Function to get a set of nodes.
231 *
232 * This returns a set of nodes based on the provided type and array of term
233 * ID's.
234 *
235 * @param $type
236 * Array representing the node types
237 * @param $terms
238 * Array of Term ID's
239 * @param $exclude
240 * Array - Optional: An array of Node ID's to exclude. Useful for excluding the node you might be comparing to currently. Default: No exclusions.
241 * @param $limit
242 * Integer - Optional: Integer controlling the maximum number of nodes returned. Default: 5
243 * @param $languages
244 * Array - Optional: An array of languages to restrict nodes to.
245 * An empty string in the array corresponds to Language Neutral nodes.
246 * An empty array will include all nodes regardless of language.
247 *
248 * @return mixed
249 * FALSE if no result or error or an associative array with node ID's as keys and the value's as arrays of nid's, vid's, title's, type's & term match counts.
250 */
251 function relevant_content_get_nodes($types, $terms, $exclude = array(), $limit = 5, $languages = array()) {
252 // If terms or types are empty, there isn't anything to match to so not a lot of point continuing.
253 if (empty($terms) || empty($types)) return FALSE;
254
255 // Define the query
256 $query = db_select('node', 'n');
257 $query->leftJoin('taxonomy_index', 'ti', 'n.nid = ti.nid');
258 $query->fields('n', array('nid', 'vid', 'title', 'type'));
259 $query->addExpression('COUNT(*)', 'cnt');
260
261 // Filter for all nodes which are published, in the "types" array and has at least one of the selected terms
262 $query->condition('n.status', 1)->condition('n.type', $types, 'IN')->condition('ti.tid', $terms, 'IN');
263
264 // Exclude any specific node id's
265 if (!empty($exclude)) { $query->condition('n.nid', $exclude, 'NOT IN'); }
266
267 // IF language is specified, make sure we filter for those too
268 if (!empty($languages)) { $query->condition('n.language', $languages, 'IN'); }
269
270 // Group, order and limit the query
271 $query->groupBy('n.nid')->orderBy('cnt', 'DESC')->orderBy('n.created', 'DESC')->orderBy('n.nid', 'DESC')->range(0, $limit);
272
273 // Execute and loop to store the results against the node ID key
274 $nodes = $query->execute()->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
275
276 // Return the node array or FALSE if there is no result/an error.
277 return empty($nodes) ? FALSE : $nodes;
278 }
279
280
281 /**
282 * Function to locally cache terms
283 * TODO: Use the Drupal Static Cache function
284 *
285 * This allows either this module or any other module to add terms to the cache.
286 * This cache is used to determine which nodes appear in the relevant content
287 * blocks.
288 *
289 * @param $terms
290 * Array of term id's
291 * @param $clear
292 * Boolean flag - can be set to force a clearing of the local cache.
293 * @return
294 * Array of the term id's currently in the cache
295 */
296 function relevant_content_term_cache($terms = array(), $clear = FALSE) {
297 static $term_cache = array();
298
299 if ($clear) {
300 $term_cache = array();
301 }
302
303 if (!empty($terms)) {
304 $term_cache = array_merge($term_cache, $terms);
305 }
306
307 return $term_cache;
308 }
309
310
311 /**
312 * Private function used as a callback for array_map which sets the item to the value of $value using $key as either an array offset or an object property.
313 *
314 * @param $item
315 * This is a variable reference to the item being mapped to. Chaning this value will change the value in the array being mapped using array_map
316 * @param $key
317 * They key of $item in the array being mapped
318 * @param $values
319 * A user defined array passed in. In this case, it us used for reference purposes
320 */
321 function _relevant_content_array_map_key_to_values(&$item, $key, $values) {
322 if (isset($values[$key])) {
323 if (is_object($values[$key])) {
324 $item = $values[$key]->name;
325 }
326 else {
327 $item = $values[$key];
328 }
329 }
330 else {
331 $item = t('Error: Item missing.');
332 }
333 }
334
335
336 /**
337 * Implement hook_field_info().
338 *
339 * Field settings: ...
340 */
341 function relevant_content_field_info() {
342 return array(
343 'relevant_content' => array(
344 'label' => t('Relevant Content'),
345 'description' => t('This is an output-only field which looks up related content for the node the field is attached to.'),
346 'default_widget' => 'relevant_content_list',
347 'default_formatter' => 'relevant_content_default',
348 'settings' => array(),
349 'instance_settings' => array(),
350 ),
351 );
352 }
353
354
355 /**
356 * Implement hook_field_settings_form().
357 */
358 function relevant_content_field_settings_form($field, $instance, $has_data) {
359 $form = array();
360
361 // Node Type Filter
362 $form['relevant_nodetypes'] = array(
363 '#type' => 'checkboxes',
364 '#title' => t('Relevant Node Types'),
365 '#default_value' => isset($field['settings']['relevant_nodetypes']) ? $field['settings']['relevant_nodetypes'] : array(),
366 '#options' => node_type_get_names(),
367 '#required' => TRUE,
368 '#description' => t('Select the node types you would like to include in this <em>Relevant Content Field</em>'),
369 );
370
371 // Load the system vocabs
372 $vocabs = array();
373 foreach (taxonomy_get_vocabularies() as $vid => $voc) {
374 $vocabs[$vid] = $voc->name;
375 }
376
377 // If there are no vocabularies configured, show an error
378 if (empty($vocabs)) {
379 // TODO: Show error
380 }
381 // Otherwise, show a vocabulary selector
382 else {
383 $form['relevant_vocabs'] = array(
384 '#type' => 'checkboxes',
385 '#title' => t('Relevant Vocabularies'),
386 '#default_value' => isset($field['settings']['relevant_vocabs']) ? $field['settings']['relevant_vocabs'] : array(),
387 '#options' => $vocabs,
388 '#required' => TRUE,
389 '#description' => t('Select the vocabularies you would like to include in this <em>Relevant Content Field</em>. Only terms in the selected vocabularies will be used to find relevant content.'),
390 );
391 }
392
393 // Define a result limitor field
394 $form['relevant_result_limit'] = array(
395 '#type' => 'textfield',
396 '#title' => t('Results Per Page'),
397 '#description' => t('Relevant content to display per page. Must be more than 0'),
398 '#default_value' => isset($field['settings']['relevant_result_limit']) ? $field['settings']['relevant_result_limit'] : 5,
399 '#required' => TRUE,
400 );
401
402 // Define a result header field with formatter options
403 $form['relevant_header'] = array(
404 '#tree' => TRUE,
405 '#title' => t('Header Text'),
406 '#type' => 'fieldset',
407 '#description' => t('Optionally include some text to appear above the list and below the label (if the label is enabled).'),
408 );
409 $form['relevant_header']['body'] = array(
410 '#type' => 'textarea',
411 '#default_value' => isset($field['settings']['relevant_header']['body']) ? $field['settings']['relevant_header']['body'] : '',
412 );
413 $form['relevant_header']['format'] = filter_form(
414 isset($field['settings']['relevant_header']['format']) ? $field['settings']['relevant_header']['format'] : NULL,
415 NULL,
416 array('field', 'settings', 'relevant_header', 'format')
417 );
418
419 return $form;
420 }
421
422
423 /**
424 * Implement hook_field_widget_settings_form().
425 */
426 function relevant_content_field_instance_settings_form($field, $instance) {
427 $form = array();
428 $form['token_settings'] = array(
429 '#type' => 'fieldset',
430 '#title' => t('Token Output Settings'),
431 '#collapsible' => TRUE,
432 '#collapsed' => TRUE,
433 );
434 $form['token_settings']['token_teaser'] = array(
435 '#type' => 'fieldset',
436 '#title' => t('Tokens for teaser view formatter'),
437 '#tree' => TRUE,
438 );
439 $form['token_settings']['token_teaser']['body'] = array(
440 '#type' => 'textarea',
441 '#description' => t('Tokens entered in here will be used for the optional token teaser formatter. This allows customized output.'),
442 '#default_value' => isset($instance['settings']['token_settings']['token_teaser']['body']) ? $instance['settings']['token_settings']['token_teaser']['body'] : '',
443 '#rows' => 4,
444 );
445 $form['token_settings']['token_teaser']['format'] = filter_form(
446 isset($instance['settings']['token_settings']['token_teaser']['format']) ? $instance['settings']['token_settings']['token_teaser']['format'] : NULL,
447 NULL,
448 array('instance', 'settings', 'token_settings', 'token_teaser', 'format')
449 );
450
451
452 $form['token_settings']['token_full'] = array(
453 '#type' => 'fieldset',
454 '#title' => t('Tokens for full view formatter'),
455 '#tree' => TRUE,
456 );
457 $form['token_settings']['token_full']['body'] = array(
458 '#type' => 'textarea',
459 '#description' => t('Tokens entered in here will be used for the optional token full formatter. This allows customized output.'),
460 '#default_value' => isset($instance['settings']['token_settings']['token_full']['body']) ? $instance['settings']['token_settings']['token_full']['body'] : '',
461 '#rows' => 4,
462 );
463 $form['token_settings']['token_full']['format'] = filter_form(
464 isset($instance['settings']['token_settings']['token_full']['format']) ? $instance['settings']['token_settings']['token_full']['format'] : '',
465 NULL,
466 array('instance', 'settings', 'token_settings', 'token_full', 'format')
467 );
468
469 $form['token_settings']['token_help'] = array(
470 '#type' => 'fieldset',
471 '#title' => t('Token Help'),
472 '#collapsed' => TRUE,
473 '#collapsible' => TRUE,
474 );
475 $form['token_settings']['token_help']['view'] = array(
476 '#type' => 'markup',
477 '#markup' => theme('relevant_content_token_help'),
478 );
479
480 return $form;
481 }
482
483
484 /**
485 * Implement hook_field_is_empty().
486 */
487 // TODO
488 function relevant_content_field_is_empty($item, $field) {
489 return FALSE;
490 }
491
492
493 /**
494 * Implement hook_field_formatter_info().
495 */
496 function relevant_content_field_formatter_info() {
497 $defaults = array('field types' => array('relevant_content'), 'behaviors' => array('multiple values' => FIELD_BEHAVIOR_CUSTOM));
498
499 return array(
500 'relevant_content_default' => $defaults + array('label' => t('Node Title - Link')),
501 'relevant_content_plain' => $defaults + array('label' => t('Node Title - Plain')),
502 'relevant_content_teaser' => $defaults + array('label' => t('Node Teaser')),
503 'relevant_content_full' => $defaults + array('label' => t('Node Full')),
504 'relevant_content_token_teaser' => $defaults + array('label' => t('Node Token Teaser')),
505 'relevant_content_token_full' => $defaults + array('label' => t('Node Token Full')),
506 );
507 }
508
509
510 /**
511 * Implement hook_field_load().
512 */
513 function relevant_content_field_load($obj_type, $objects, $field, $instances, $langcode, &$items, $age) {
514 foreach ($objects as $id => $object) {
515 //Get the terms using the handy term wrapper in the parent module
516 $terms = relevant_content_get_page_terms($object);
517
518 // If there are no terms, return an empty item set - there will be nothing in common with this.
519 if (empty($terms)) return;
520
521 // Grab the types & vocabs
522 $types = array_values(array_filter($field['settings']['relevant_nodetypes']));
523 $vocabs = array_values(array_filter($field['settings']['relevant_vocabs']));
524
525 // Filter the terms from the vocabs associated with this field.
526 foreach ($terms as $tid => $term) {
527 if (in_array($term->vid, $vocabs)) {
528 $terms[$tid] = $tid;
529 }
530 else {
531 unset($terms[$tid]);
532 }
533 }
534
535 // If there are no terms *after* filtering, return an empty item set - there will be nothing in common with this.
536 if (empty($terms)) return;
537
538 // Search for items and return the item set.
539 $relevant_items = relevant_content_get_nodes($types, $terms, array($object->nid), $field['settings']['relevant_result_limit']);
540 if (empty($relevant_items)) return;
541
542 // Set the items which will get passed to the theme layer
543 $items[$id]['items'] = $relevant_items;
544 $items[$id]['header'] = $field['settings']['relevant_header'];
545 $items[$id]['#settings'] = $instances[$id]['settings']['token_settings'];
546 }
547 }
548
549
550 /**
551 * Implement hook_field_widget_info().
552 */
553 function relevant_content_field_widget_info() {
554 return array(
555 'relevant_content_list' => array(
556 'label' => t('Relevant Nodes (Read Only)'),
557 'field types' => array('relevant_content'),
558 'behaviors' => array(
559 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
560 ),
561 ),
562 );
563 }
564
565
566 /**
567 * Implement hook_element_info().
568 */
569 function relevant_content_element_info() {
570 return array(
571 'relevant_content_list' => array(
572 '#input' => FALSE,
573 ),
574 );
575 }
576
577
578 function relevant_content_preprocess_field(&$variables) {
579 // If the field is a relevant content field, we need to add more suggestion types (so we can easily theme all RC fields).
580 // This is necessary so that we can override the default content-field template (which doesn't work correctly for us)
581 if ($variables['field_type'] == 'relevant_content') {
582 $suggestions = array(
583 'field-relevant-content',
584 'field-relevant-content-'. $variables['field_name'],
585 'field-relevant-content-'. $variables['instance']['bundle'],
586 'field-relevant-content-'. $variables['field_name'] .'-'. $variables['instance']['bundle'],
587 );
588
589 $variables['template_files'] = array_merge($variables['template_files'], $suggestions);
590 $header = $variables['element']['items']['header']['#item'];
591 $variables['header'] = check_markup($header['body'], $header['format']);
592 }
593 drupal_add_css(drupal_get_path('module', 'relevant_content') .'/relevant_content.css');
594 }
595
596
597 function relevant_content_theme_registry_alter(&$theme_registry) {
598 // This seems necessary to allow the theme registry to also search the RC folder for template files.
599 $theme_registry['field']['theme paths'][] = drupal_get_path('module', 'relevant_content');
600 }

  ViewVC Help
Powered by ViewVC 1.1.2