| 1 |
<?php
|
| 2 |
// $Id: nodereferrer.module,v 1.19 2009/06/17 10:17:50 andypost Exp $
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
/**
|
| 7 |
* @file
|
| 8 |
* Defines a field type for backlinking referencing nodes.
|
| 9 |
*
|
| 10 |
* @todo
|
| 11 |
* -clear content cache with nodeapi.
|
| 12 |
* -query nids for access on load/view..
|
| 13 |
*/
|
| 14 |
|
| 15 |
/**
|
| 16 |
* Implementation of hook_help().
|
| 17 |
*/
|
| 18 |
function nodereferrer_help($path, $arg) {
|
| 19 |
switch ($path) {
|
| 20 |
case 'admin/modules#description':
|
| 21 |
return t('<strong>CCK:</strong> Defines a field type for displaying referrers to a node. <em>Note: Requires content.module.</em>');
|
| 22 |
}
|
| 23 |
}
|
| 24 |
|
| 25 |
/**
|
| 26 |
* Implementation of hook_field_info().
|
| 27 |
*/
|
| 28 |
function nodereferrer_field_info() {
|
| 29 |
return array(
|
| 30 |
'nodereferrer' => array('label' => t('Node Referrers')),
|
| 31 |
);
|
| 32 |
}
|
| 33 |
|
| 34 |
/**
|
| 35 |
* Implementation of hook_field_settings().
|
| 36 |
*/
|
| 37 |
function nodereferrer_field_settings($op, $field) {
|
| 38 |
switch ($op) {
|
| 39 |
case 'views data':
|
| 40 |
$data = content_views_field_views_data($field);
|
| 41 |
if (is_array($data)) {
|
| 42 |
foreach ($data as $k => $v) {
|
| 43 |
$data[$k] = array();
|
| 44 |
}
|
| 45 |
}
|
| 46 |
else {
|
| 47 |
$data = array();
|
| 48 |
}
|
| 49 |
return $data;
|
| 50 |
|
| 51 |
case 'callbacks':
|
| 52 |
return array('view' => CONTENT_CALLBACK_CUSTOM);
|
| 53 |
|
| 54 |
case 'form':
|
| 55 |
$form = array();
|
| 56 |
// Hide unused options
|
| 57 |
$form['required'] = array(
|
| 58 |
'#type' => 'hidden',
|
| 59 |
'#value' => FALSE,
|
| 60 |
);
|
| 61 |
$form['multiple'] = array(
|
| 62 |
'#type' => 'hidden',
|
| 63 |
'#value' => 0,
|
| 64 |
);
|
| 65 |
|
| 66 |
$form['referrer_types'] = array(
|
| 67 |
'#type' => 'checkboxes',
|
| 68 |
'#title' => t('Referring Node Types'),
|
| 69 |
'#multiple' => TRUE,
|
| 70 |
'#default_value' => is_array($field['referrer_types']) ? $field['referrer_types'] : array(),
|
| 71 |
'#options' => node_get_types('names'),
|
| 72 |
);
|
| 73 |
|
| 74 |
$options = nodereferrer_nodereference_field_options();
|
| 75 |
$form['referrer_fields'] = array(
|
| 76 |
'#type' => 'checkboxes',
|
| 77 |
'#title' => t('Referring Fields'),
|
| 78 |
'#multiple' => TRUE,
|
| 79 |
'#default_value' => is_array($field['referrer_fields']) ? $field['referrer_fields'] : array(),
|
| 80 |
'#options' => $options,
|
| 81 |
);
|
| 82 |
|
| 83 |
if (module_exists('translation')) {
|
| 84 |
$form['referrer_translations'] = array(
|
| 85 |
'#type' => 'checkbox',
|
| 86 |
'#title' => t('Show on translations'),
|
| 87 |
'#description' => t('If this is checked, referrers will also show on translations of the referenced node'),
|
| 88 |
'#default_value' => is_int($field['referrer_translations']) ? $field['referrer_translations'] : 0,
|
| 89 |
);
|
| 90 |
}
|
| 91 |
|
| 92 |
$form['referrer_nodes_per_page'] = array(
|
| 93 |
'#type' => 'textfield',
|
| 94 |
'#title' => t('Referrers Per Page'),
|
| 95 |
'#description' => t('Referring nodes to display per page. 0 for no paging.'),
|
| 96 |
'#default_value' => !empty($field['referrer_nodes_per_page']) ? $field['referrer_nodes_per_page'] : 0,
|
| 97 |
);
|
| 98 |
|
| 99 |
$form['referrer_pager_element'] = array(
|
| 100 |
'#type' => 'textfield',
|
| 101 |
'#title' => t('Pager element'),
|
| 102 |
'#description' => t('Use this to avoid clashes if you have several pagers on one page'),
|
| 103 |
'#default_value' => !empty($field['referrer_pager_element']) ? $field['referrer_pager_element'] : 0,
|
| 104 |
);
|
| 105 |
|
| 106 |
$form['referrer_order'] = array(
|
| 107 |
'#type' => 'select',
|
| 108 |
'#title' => t('Refferer Sort Order'),
|
| 109 |
'#options' => array(
|
| 110 |
'CREATED_ASC' => t('Chronological Order'),
|
| 111 |
'CREATED_DESC' => t('Reverse Chronological Order'),
|
| 112 |
'TITLE_ASC' => t('Title Order'),
|
| 113 |
'TITLE_DESC' => t('Reverse Title Order'),
|
| 114 |
),
|
| 115 |
'#default_value' => strlen($field['referrer_order']) ? $field['referrer_order'] : 'TITLE_ASC',
|
| 116 |
);
|
| 117 |
|
| 118 |
return $form;
|
| 119 |
|
| 120 |
case 'save':
|
| 121 |
$settings = array('referrer_types', 'referrer_fields', 'referrer_nodes_per_page', 'referrer_pager_element', 'referrer_order');
|
| 122 |
if (module_exists('translation')) {
|
| 123 |
$settings[] = 'referrer_translations';
|
| 124 |
}
|
| 125 |
return $settings;
|
| 126 |
}
|
| 127 |
}
|
| 128 |
|
| 129 |
/**
|
| 130 |
* Implementation of hook_field().
|
| 131 |
*/
|
| 132 |
function nodereferrer_field($op, &$node, $field, &$items, $teaser, $page) {
|
| 133 |
switch ($op) {
|
| 134 |
case 'load':
|
| 135 |
$types = array_values(array_filter($field['referrer_types']));
|
| 136 |
$fields = array_values(array_filter($field['referrer_fields']));
|
| 137 |
$order = $field['referrer_order'];
|
| 138 |
$translations = isset($field['referrer_translations']) ? $field['referrer_translations'] : 0;
|
| 139 |
$values = nodereferrer_referrers($node->nid, $fields, $types, $translations, $order);
|
| 140 |
// Pass referring node objects into CCK content_load() cache. 24/08/2006 sun
|
| 141 |
$items = array();
|
| 142 |
foreach ($values as $nid => $rnode) {
|
| 143 |
$items[] = $rnode;
|
| 144 |
}
|
| 145 |
|
| 146 |
if (count($items) == 0) {
|
| 147 |
return array($field['field_name'] => array());
|
| 148 |
}
|
| 149 |
|
| 150 |
$output = array(
|
| 151 |
'items' => $items,
|
| 152 |
'limit' => empty($field['referrer_nodes_per_page']) ? 0 : $field['referrer_nodes_per_page'],
|
| 153 |
'element' => empty($field['referrer_pager_element']) ? 0 : $field['referrer_pager_element'],
|
| 154 |
'pager' => '',
|
| 155 |
);
|
| 156 |
|
| 157 |
return array($field['field_name'] => array($output));
|
| 158 |
|
| 159 |
case 'delete':
|
| 160 |
case 'update':
|
| 161 |
// clear cache on nodes that refer to me.
|
| 162 |
$types = array_values(array_filter($field['referrer_types']));
|
| 163 |
$fields = array_values(array_filter($field['referrer_fields']));
|
| 164 |
|
| 165 |
// clear any modules referring to me as my title or other data may change.
|
| 166 |
// and nodereference doesn't clear the cache yet.
|
| 167 |
foreach (nodereferrer_referrers($node->nid, $fields, $types, false) as $delta => $item) {
|
| 168 |
$cid = 'content:' . $item['nid'] . ':' . $item['vid'];
|
| 169 |
cache_clear_all($cid, 'cache_page');
|
| 170 |
}
|
| 171 |
return;
|
| 172 |
}
|
| 173 |
}
|
| 174 |
|
| 175 |
/**
|
| 176 |
* Implementation of hook_field_formatter_info().
|
| 177 |
*/
|
| 178 |
function nodereferrer_field_formatter_info() {
|
| 179 |
return array(
|
| 180 |
'default' => array(
|
| 181 |
'label' => 'Node Title Link (Default)',
|
| 182 |
'field types' => array('nodereferrer'),
|
| 183 |
'multiple values' => CONTENT_HANDLE_CORE,
|
| 184 |
),
|
| 185 |
'plain' => array(
|
| 186 |
'label' => 'Node Title Plain Text',
|
| 187 |
'field types' => array('nodereferrer'),
|
| 188 |
'multiple values' => CONTENT_HANDLE_CORE,
|
| 189 |
),
|
| 190 |
'teaser' => array(
|
| 191 |
'label' => 'Node Teaser',
|
| 192 |
'field types' => array('nodereferrer'),
|
| 193 |
'multiple values' => CONTENT_HANDLE_CORE,
|
| 194 |
),
|
| 195 |
'full' => array(
|
| 196 |
'label' => 'Node Body',
|
| 197 |
'field types' => array('nodereferrer'),
|
| 198 |
'multiple values' => CONTENT_HANDLE_CORE,
|
| 199 |
),
|
| 200 |
);
|
| 201 |
}
|
| 202 |
|
| 203 |
/**
|
| 204 |
* Implementation of hook_theme().
|
| 205 |
*/
|
| 206 |
function nodereferrer_theme() {
|
| 207 |
return array(
|
| 208 |
'nodereferrer_formatter_default' => array(
|
| 209 |
'arguments' => array('info' => NULL),
|
| 210 |
),
|
| 211 |
'nodereferrer_field_default' => array(
|
| 212 |
'arguments' => array('element' => NULL),
|
| 213 |
),
|
| 214 |
'nodereferrer_formatter_plain' => array(
|
| 215 |
'arguments' => array('element' => NULL),
|
| 216 |
),
|
| 217 |
'nodereferrer_field_plain' => array(
|
| 218 |
'arguments' => array('element' => NULL),
|
| 219 |
),
|
| 220 |
'nodereferrer_formatter_teaser' => array(
|
| 221 |
'arguments' => array('element' => NULL),
|
| 222 |
),
|
| 223 |
'nodereferrer_field_teaser' => array(
|
| 224 |
'arguments' => array('element' => NULL),
|
| 225 |
),
|
| 226 |
'nodereferrer_formatter_full' => array(
|
| 227 |
'arguments' => array('element' => NULL),
|
| 228 |
),
|
| 229 |
'nodereferrer_field_full' => array(
|
| 230 |
'arguments' => array('element' => NULL),
|
| 231 |
),
|
| 232 |
);
|
| 233 |
}
|
| 234 |
|
| 235 |
/**
|
| 236 |
* Generic formatter function
|
| 237 |
*/
|
| 238 |
function nodereferrer_theme_formatter($formatter, $info) {
|
| 239 |
$items = isset($info['#item']['items']) ? $info['#item']['items'] : array();
|
| 240 |
$limit = $info['#item']['limit'];
|
| 241 |
$element = $info['#item']['element'];
|
| 242 |
$pager = '';
|
| 243 |
|
| 244 |
if ($limit) {
|
| 245 |
// Fake the values set by pager query...
|
| 246 |
global $pager_page_array, $pager_total, $pager_total_items;
|
| 247 |
$page = isset($_GET['page']) ? $_GET['page'] : '';
|
| 248 |
|
| 249 |
// Convert comma-separated $page to an array, used by other functions.
|
| 250 |
$pager_page_array = explode(',', $page);
|
| 251 |
// We calculate the total of pages as ceil(items / limit).
|
| 252 |
$pager_total_items[$element] = count($items);
|
| 253 |
$pager_total[$element] = ceil($pager_total_items[$element] / $limit);
|
| 254 |
$pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1));
|
| 255 |
|
| 256 |
// only display the select elements.
|
| 257 |
if (is_array($items)) {
|
| 258 |
$items = array_slice($items, $pager_page_array[$element] * $limit, $limit);
|
| 259 |
}
|
| 260 |
|
| 261 |
$pager = theme('pager', array(), $limit, $element);
|
| 262 |
}
|
| 263 |
|
| 264 |
$themed_items = array();
|
| 265 |
foreach ($items as $i) {
|
| 266 |
$i['field'] = $info; // Add some extra information the themer might like to have
|
| 267 |
$themed_items[] = theme('nodereferrer_field_' . $formatter, $i);
|
| 268 |
}
|
| 269 |
$out = theme('item_list', $themed_items) . $pager;
|
| 270 |
|
| 271 |
return $out;
|
| 272 |
}
|
| 273 |
|
| 274 |
/**
|
| 275 |
* Theme functions for 'default' field formatter.
|
| 276 |
*/
|
| 277 |
function theme_nodereferrer_formatter_default($info) {
|
| 278 |
return nodereferrer_theme_formatter('default', $info);
|
| 279 |
}
|
| 280 |
|
| 281 |
function theme_nodereferrer_field_default($element) {
|
| 282 |
return l($element['title'], 'node/' . $element['nid']);
|
| 283 |
}
|
| 284 |
|
| 285 |
/**
|
| 286 |
* Theme function for 'plain' field formatter.
|
| 287 |
*/
|
| 288 |
function theme_nodereferrer_formatter_plain($info) {
|
| 289 |
return nodereferrer_theme_formatter('plain', $info);
|
| 290 |
}
|
| 291 |
|
| 292 |
function theme_nodereferrer_field_plain($element) {
|
| 293 |
return strip_tags($element['title']);
|
| 294 |
}
|
| 295 |
|
| 296 |
/**
|
| 297 |
* Theme function for 'teaser' field formatter.
|
| 298 |
*/
|
| 299 |
function theme_nodereferrer_formatter_teaser($info) {
|
| 300 |
return nodereferrer_theme_formatter('teaser', $info);
|
| 301 |
}
|
| 302 |
|
| 303 |
function theme_nodereferrer_field_teaser($element) {
|
| 304 |
return node_view(node_load($element['nid']), TRUE);
|
| 305 |
}
|
| 306 |
|
| 307 |
/**
|
| 308 |
* Theme function for 'full' field formatter.
|
| 309 |
*/
|
| 310 |
function theme_nodereferrer_formatter_full($info) {
|
| 311 |
return nodereferrer_theme_formatter('full', $info);
|
| 312 |
}
|
| 313 |
|
| 314 |
function theme_nodereferrer_field_full($element) {
|
| 315 |
return node_view(node_load($element['nid']));
|
| 316 |
}
|
| 317 |
|
| 318 |
/**
|
| 319 |
* Implementation of hook_widget_info().
|
| 320 |
*/
|
| 321 |
function nodereferrer_widget_info() {
|
| 322 |
return array(
|
| 323 |
'nodereferrer_list' => array(
|
| 324 |
'label' => t('Read-Only List'),
|
| 325 |
'field types' => array('nodereferrer'),
|
| 326 |
'multiple values' => CONTENT_HANDLE_MODULE,
|
| 327 |
'callbacks' => array(
|
| 328 |
'default value' => CONTENT_CALLBACK_NONE,
|
| 329 |
),
|
| 330 |
),
|
| 331 |
);
|
| 332 |
}
|
| 333 |
|
| 334 |
/**
|
| 335 |
* Implementation of hook_content_is_empty().
|
| 336 |
*/
|
| 337 |
function nodereferrer_content_is_empty($item, $field) {
|
| 338 |
return TRUE;
|
| 339 |
}
|
| 340 |
|
| 341 |
/**
|
| 342 |
* Get an array of referrer nids, by node.type & field.type
|
| 343 |
* @param nid
|
| 344 |
* the nid we want to find referres for
|
| 345 |
* @param fieldnames
|
| 346 |
* array of fieldnames to be checked for referrers
|
| 347 |
* @param nodetypes
|
| 348 |
* array of node types to be checked for referrers
|
| 349 |
* @param translations
|
| 350 |
* boolean if true, also return nodes that referrer to translations of the given node
|
| 351 |
*/
|
| 352 |
|
| 353 |
function nodereferrer_referrers($nid, $fieldnames = array(), $nodetypes = array(), $translations = 0, $order = 'DESC') {
|
| 354 |
if ($nodetypes) {
|
| 355 |
$filter_nodetypes = "AND n.type IN ('" . implode("', '", $nodetypes) . "')";
|
| 356 |
}
|
| 357 |
else {
|
| 358 |
$filter_nodetypes = '';
|
| 359 |
}
|
| 360 |
$fields = content_fields();
|
| 361 |
// Set default values of fieldnames.
|
| 362 |
if (!count($fieldnames)) {
|
| 363 |
$fieldnames = array_keys($fields);
|
| 364 |
}
|
| 365 |
|
| 366 |
switch ($order) {
|
| 367 |
case 'TITLE_ASC':
|
| 368 |
$order = 'n.title ASC';
|
| 369 |
break;
|
| 370 |
|
| 371 |
case 'TITLE_DESC':
|
| 372 |
$order = 'n.title DESC';
|
| 373 |
break;
|
| 374 |
|
| 375 |
case 'ASC':
|
| 376 |
case 'CREATED_ASC':
|
| 377 |
$order = 'n.created ASC';
|
| 378 |
break;
|
| 379 |
|
| 380 |
default :
|
| 381 |
case 'DESC':
|
| 382 |
case 'CREATED_DESC':
|
| 383 |
$order = 'n.created DESC';
|
| 384 |
break;
|
| 385 |
}
|
| 386 |
|
| 387 |
|
| 388 |
$values = array();
|
| 389 |
foreach ($fieldnames as $fieldname) {
|
| 390 |
if ($fields[$fieldname]['type'] == 'nodereference') {
|
| 391 |
$db_info = content_database_info($fields[$fieldname]);
|
| 392 |
|
| 393 |
if ($translations) {
|
| 394 |
$query = "SELECT n.nid, n.vid, n.title
|
| 395 |
FROM {" . $db_info['table'] . "} nr
|
| 396 |
INNER JOIN {node} current_node ON current_node.nid = %d
|
| 397 |
INNER JOIN {node} n ON n.vid = nr.vid AND n.status = 1 " . $filter_nodetypes . "
|
| 398 |
LEFT JOIN {node} translations ON current_node.tnid > 0 AND translations.tnid = current_node.tnid
|
| 399 |
WHERE (current_node.tnid = 0 AND nr." . $db_info['columns']['nid']['column'] . " = current_node.nid)
|
| 400 |
OR
|
| 401 |
(current_node.tnid > 0 AND nr." . $db_info['columns']['nid']['column'] . " = translations.nid)
|
| 402 |
ORDER BY " . $order;
|
| 403 |
}
|
| 404 |
else {
|
| 405 |
$query = "SELECT n.nid, n.vid, n.title
|
| 406 |
FROM {" . $db_info['table'] . "} nr
|
| 407 |
INNER JOIN {node} n ON n.vid = nr.vid AND n.status = 1 " . $filter_nodetypes . "
|
| 408 |
WHERE nr." . $db_info['columns']['nid']['column'] . " = %d
|
| 409 |
ORDER BY " . $order;
|
| 410 |
}
|
| 411 |
|
| 412 |
$query = db_rewrite_sql($query);
|
| 413 |
$result = db_query($query, $nid);
|
| 414 |
|
| 415 |
while ($value = db_fetch_array($result)) {
|
| 416 |
// avoid duplicate referrers by using nid as key
|
| 417 |
$values[$value['nid']] = $value;
|
| 418 |
}
|
| 419 |
}
|
| 420 |
}
|
| 421 |
return $values;
|
| 422 |
}
|
| 423 |
|
| 424 |
|
| 425 |
/**
|
| 426 |
* Helper function to create an options list of nodereference fields.
|
| 427 |
*/
|
| 428 |
function nodereferrer_nodereference_field_options() {
|
| 429 |
$options = array();
|
| 430 |
$types = content_fields();
|
| 431 |
foreach($types as $type) {
|
| 432 |
if ($type['type'] == 'nodereference') {
|
| 433 |
$options[$type['field_name']] = $type['field_name'] . ' (' . $type['widget']['label'] . ')';
|
| 434 |
}
|
| 435 |
}
|
| 436 |
return $options;
|
| 437 |
}
|
| 438 |
|
| 439 |
/**
|
| 440 |
* Implementation of hook_nodeapi().
|
| 441 |
*/
|
| 442 |
function nodereferrer_nodeapi($node, $op) {
|
| 443 |
switch ($op) {
|
| 444 |
case 'prepare':
|
| 445 |
case 'insert':
|
| 446 |
case 'update':
|
| 447 |
case 'delete':
|
| 448 |
// Clear content cache to help maintain proper display of nodes.
|
| 449 |
$nids = array();
|
| 450 |
$type = content_types($node->type);
|
| 451 |
foreach ($type['fields'] as $field) {
|
| 452 |
// Add referenced nodes to nids. This will clean up nodereferrer fields
|
| 453 |
// when the referencing node is updated.
|
| 454 |
if ($field['type'] == 'nodereference') {
|
| 455 |
$node_field = isset($node->$field['field_name']) ? $node->$field['field_name'] : array();
|
| 456 |
foreach ($node_field as $delta => $item) {
|
| 457 |
$nids[$item['nid']] = $item['nid'];
|
| 458 |
}
|
| 459 |
}
|
| 460 |
}
|
| 461 |
|
| 462 |
// Clear Content cache for nodes that reference the node that is being updated.
|
| 463 |
// This will keep nodereference fields up to date when referred nodes are
|
| 464 |
// updated. @note this currenlty doesn't work all that well since nodereference
|
| 465 |
// doesn't respect publishing states or access control.
|
| 466 |
if (isset($node->nid)) {
|
| 467 |
$referrers = nodereferrer_referrers($node->nid);
|
| 468 |
$referrer_nids = array_keys($referrers);
|
| 469 |
$nids = array_merge($nids, $referrer_nids);
|
| 470 |
}
|
| 471 |
|
| 472 |
foreach ($nids as $nid) {
|
| 473 |
$cid = "content:$nid:";
|
| 474 |
// define a table to delete from or else this complains
|
| 475 |
cache_clear_all($cid, 'cache_content', TRUE);
|
| 476 |
}
|
| 477 |
}
|
| 478 |
}
|
| 479 |
|
| 480 |
/**
|
| 481 |
* Implementation of hook_views_api().
|
| 482 |
*/
|
| 483 |
function nodereferrer_views_api() {
|
| 484 |
return array(
|
| 485 |
'api' => '2.0',
|
| 486 |
'path' => drupal_get_path('module', 'nodereferrer') . '/views',
|
| 487 |
);
|
| 488 |
}
|
| 489 |
|