| 1 |
<?php
|
| 2 |
// $Id$
|
| 3 |
|
| 4 |
//////////////////////////////////////////////////////////////////////////////
|
| 5 |
// Module settings
|
| 6 |
|
| 7 |
define('RELATIONS_REPOSITORY', 'relations');
|
| 8 |
define('RELATIONS_PREDICATE', 'http://www.w3.org/2000/01/rdf-schema#seeAlso');
|
| 9 |
|
| 10 |
//////////////////////////////////////////////////////////////////////////////
|
| 11 |
// Core API hooks
|
| 12 |
|
| 13 |
/**
|
| 14 |
* Implementation of hook_help().
|
| 15 |
*/
|
| 16 |
function relations_help($path, $arg = NULL) {
|
| 17 |
switch ($path) {
|
| 18 |
case 'node/%/relations#table':
|
| 19 |
return '<p>' . t('To create a new relationship, type in the title or URL of another content item in the textbox above. You must have editor privileges for either the source or target item, and at least reader privileges for the other item.') . '</p>';
|
| 20 |
}
|
| 21 |
}
|
| 22 |
|
| 23 |
/**
|
| 24 |
* Implementation of hook_perm().
|
| 25 |
*/
|
| 26 |
function relations_perm() {
|
| 27 |
return array(
|
| 28 |
'view node relations',
|
| 29 |
'create node relations',
|
| 30 |
'delete node relations',
|
| 31 |
);
|
| 32 |
}
|
| 33 |
|
| 34 |
/**
|
| 35 |
* Implementation of hook_menu().
|
| 36 |
*/
|
| 37 |
function relations_menu() {
|
| 38 |
return array(
|
| 39 |
'node/%node/relations' => array(
|
| 40 |
'title' => 'Relations',
|
| 41 |
'description' => 'List, edit, or create node relationships.',
|
| 42 |
'type' => MENU_LOCAL_TASK,
|
| 43 |
'access callback' => 'relations_menu_access',
|
| 44 |
'access arguments' => array(1, 'view node relations', TRUE),
|
| 45 |
'page callback' => 'drupal_get_form',
|
| 46 |
'page arguments' => array('relations_node_form', 1),
|
| 47 |
'file' => 'relations.pages.inc',
|
| 48 |
'weight' => 3,
|
| 49 |
),
|
| 50 |
'node/%node/relations/autocomplete' => array(
|
| 51 |
'title' => 'Relations autocomplete',
|
| 52 |
'type' => MENU_CALLBACK,
|
| 53 |
'access callback' => 'relations_menu_access',
|
| 54 |
'access arguments' => array(1, 'create node relations', TRUE),
|
| 55 |
'page callback' => 'relations_node_title_autocomplete',
|
| 56 |
'page arguments' => array(1),
|
| 57 |
'file' => 'relations.pages.inc',
|
| 58 |
),
|
| 59 |
'node/%node/relations/delete/%node' => array(
|
| 60 |
'title' => 'Delete relation',
|
| 61 |
'type' => MENU_CALLBACK,
|
| 62 |
'access callback' => 'relations_menu_access',
|
| 63 |
'access arguments' => array(1, 'delete node relations', TRUE),
|
| 64 |
'page callback' => 'drupal_get_form',
|
| 65 |
'page arguments' => array('relations_node_delete', 1, 4),
|
| 66 |
'file' => 'relations.pages.inc',
|
| 67 |
),
|
| 68 |
|
| 69 |
// AHAH handlers for unsaved nodes
|
| 70 |
'node/add/relations/autocomplete' => array(
|
| 71 |
'title' => 'Relations autocomplete (for unsaved nodes)',
|
| 72 |
'type' => MENU_CALLBACK,
|
| 73 |
'access callback' => 'relations_menu_access',
|
| 74 |
'access arguments' => array(NULL, 'view node relations', TRUE),
|
| 75 |
'page callback' => 'relations_node_title_autocomplete',
|
| 76 |
'page arguments' => array(NULL),
|
| 77 |
'file' => 'relations.pages.inc',
|
| 78 |
),
|
| 79 |
'node/add/relations/create' => array(
|
| 80 |
'title' => 'Add relation (for unsaved nodes)',
|
| 81 |
'type' => MENU_CALLBACK,
|
| 82 |
'access callback' => 'relations_menu_access',
|
| 83 |
'access arguments' => array(NULL, 'view node relations', TRUE),
|
| 84 |
'page callback' => 'relations_node_session_create',
|
| 85 |
'page arguments' => array(),
|
| 86 |
'file' => 'relations.pages.inc',
|
| 87 |
),
|
| 88 |
'node/add/relations/delete/%node' => array(
|
| 89 |
'title' => 'Delete relation (for unsaved nodes)',
|
| 90 |
'type' => MENU_CALLBACK,
|
| 91 |
'access callback' => 'relations_menu_access',
|
| 92 |
'access arguments' => array(NULL, 'view node relations', TRUE),
|
| 93 |
'page callback' => 'relations_node_session_delete',
|
| 94 |
'page arguments' => array(4),
|
| 95 |
'file' => 'relations.pages.inc',
|
| 96 |
),
|
| 97 |
|
| 98 |
// Administer >> Site configuration >> RDF settings >> Relations
|
| 99 |
'admin/settings/rdf/relations' => array(
|
| 100 |
'title' => 'Relations',
|
| 101 |
'description' => 'Settings for the Relations API module.',
|
| 102 |
'access arguments' => array('administer site configuration'),
|
| 103 |
'page callback' => 'drupal_get_form',
|
| 104 |
'page arguments' => array('relations_admin_settings'),
|
| 105 |
'file' => 'relations.admin.inc',
|
| 106 |
),
|
| 107 |
);
|
| 108 |
}
|
| 109 |
|
| 110 |
/**
|
| 111 |
* Implementation of hook_menu_alter().
|
| 112 |
*/
|
| 113 |
function relations_menu_alter(&$items) {
|
| 114 |
if (!variable_get('relations_ui_node_tab', '1')) {
|
| 115 |
unset($items['node/%node/relations']);
|
| 116 |
}
|
| 117 |
}
|
| 118 |
|
| 119 |
/**
|
| 120 |
* Implementation of hook_block().
|
| 121 |
*/
|
| 122 |
function relations_block($op = 'list', $delta = 0, $edit = array()) {
|
| 123 |
$node_types = node_get_types('names');
|
| 124 |
|
| 125 |
switch ($op) {
|
| 126 |
case 'list':
|
| 127 |
$blocks = array(
|
| 128 |
'relations' => array(
|
| 129 |
'info' => t('Relations'),
|
| 130 |
'region' => 'right',
|
| 131 |
'weight' => -3,
|
| 132 |
'cache' => BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_USER,
|
| 133 |
),
|
| 134 |
);
|
| 135 |
foreach (relations_get_node_types() as $node_type) {
|
| 136 |
$blocks[$node_type] = array(
|
| 137 |
'info' => t('Relations: !type', array('!type' => $node_types[$node_type])),
|
| 138 |
'region' => 'right',
|
| 139 |
'weight' => -3,
|
| 140 |
'cache' => BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_USER,
|
| 141 |
);
|
| 142 |
}
|
| 143 |
return $blocks;
|
| 144 |
|
| 145 |
case 'view':
|
| 146 |
$block = array();
|
| 147 |
if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) != 'edit') {
|
| 148 |
$node = node_load(arg(1));
|
| 149 |
|
| 150 |
switch ($delta) {
|
| 151 |
case 'relations':
|
| 152 |
$block['subject'] = t('Related content');
|
| 153 |
$block['content'] = theme('relations_node_block', $node);
|
| 154 |
break;
|
| 155 |
default:
|
| 156 |
if (array_search($delta, relations_get_node_types($node)) && isset($node_types[$delta])) {
|
| 157 |
$block['subject'] = t('Related content: !type', array('!type' => $node_types[$delta]));
|
| 158 |
$block['content'] = theme('relations_node_block', $node, array('type' => $delta));
|
| 159 |
}
|
| 160 |
break;
|
| 161 |
}
|
| 162 |
}
|
| 163 |
return $block;
|
| 164 |
}
|
| 165 |
}
|
| 166 |
|
| 167 |
/**
|
| 168 |
* Implementation of hook_forms().
|
| 169 |
*/
|
| 170 |
function relations_forms() {
|
| 171 |
return array(
|
| 172 |
'relations_node_form' => array(
|
| 173 |
'callback' => 'relations_node_form',
|
| 174 |
),
|
| 175 |
);
|
| 176 |
}
|
| 177 |
|
| 178 |
/**
|
| 179 |
* Implementation of hook_theme()
|
| 180 |
*/
|
| 181 |
function relations_theme() {
|
| 182 |
return array(
|
| 183 |
'relations_node_form' => array(
|
| 184 |
'arguments' => array('node' => NULL),
|
| 185 |
'file' => 'relations.pages.inc',
|
| 186 |
),
|
| 187 |
'relations_node_table' => array(
|
| 188 |
'arguments' => array('node' => NULL),
|
| 189 |
'file' => 'relations.pages.inc',
|
| 190 |
),
|
| 191 |
'relations_node_block' => array(
|
| 192 |
'arguments' => array('node' => NULL),
|
| 193 |
'file' => 'relations.pages.inc',
|
| 194 |
),
|
| 195 |
'relations_node_list' => array(
|
| 196 |
'arguments' => array('nodes' => NULL),
|
| 197 |
'file' => 'relations.pages.inc',
|
| 198 |
),
|
| 199 |
'relations_admin_settings' => array(
|
| 200 |
'arguments' => array('form' => NULL),
|
| 201 |
'file' => 'relations.admin.inc',
|
| 202 |
),
|
| 203 |
);
|
| 204 |
}
|
| 205 |
|
| 206 |
/**
|
| 207 |
* Implementation of hook_form_alter().
|
| 208 |
*
|
| 209 |
* @see relations_nodeapi()
|
| 210 |
*/
|
| 211 |
function relations_form_alter(&$form, $form_state, $form_id) {
|
| 212 |
if (isset($form['type']) && isset($form['#node'])) {
|
| 213 |
$node = $form['#node'];
|
| 214 |
|
| 215 |
if ($form['type']['#value'] .'_node_form' == $form_id && relations_is_node_type_enabled($node->type)) {
|
| 216 |
if (!variable_get('relations_ui_node_edit', '1')) {
|
| 217 |
return; // configured at <admin/settings/rdf/relations>
|
| 218 |
}
|
| 219 |
|
| 220 |
$form['relations'] = array(
|
| 221 |
'#type' => 'fieldset',
|
| 222 |
'#access' => user_access('create node relations'),
|
| 223 |
'#title' => t('Related content'),
|
| 224 |
'#collapsible' => TRUE,
|
| 225 |
'#collapsed' => TRUE,
|
| 226 |
'#prefix' => '<div class="relations">',
|
| 227 |
'#suffix' => '</div>',
|
| 228 |
'#weight' => 20,
|
| 229 |
);
|
| 230 |
$form['relations']['relations_blocks'] = array(
|
| 231 |
'#type' => 'checkboxes',
|
| 232 |
'#title' => t('Enabled related content types'),
|
| 233 |
'#default_value' => relations_get_node_types($node),
|
| 234 |
'#options' => array_map('t', array_intersect_key(node_get_types('names'), relations_get_node_types())),
|
| 235 |
'#description' => t('Select which types of related content you wish to display when this node is viewed.'),
|
| 236 |
);
|
| 237 |
|
| 238 |
module_load_include('inc', 'relations', 'relations.pages');
|
| 239 |
$form['relations']['relations_table'] = relations_node_form(array(), $node, TRUE);
|
| 240 |
}
|
| 241 |
}
|
| 242 |
}
|
| 243 |
|
| 244 |
/**
|
| 245 |
* Implementation of hook_nodeapi().
|
| 246 |
*/
|
| 247 |
function relations_nodeapi(&$node, $op, $arg = 0) {
|
| 248 |
if (relations_is_node_type_enabled($node->type)) {
|
| 249 |
switch ($op) {
|
| 250 |
case 'prepare':
|
| 251 |
if (empty($_POST)) {
|
| 252 |
$_SESSION['relations'] = array('create' => array(), 'delete' => array());
|
| 253 |
}
|
| 254 |
$node->relations_blocks = relations_get_node_types($node);
|
| 255 |
break;
|
| 256 |
case 'insert':
|
| 257 |
case 'update':
|
| 258 |
if (!empty($_SESSION['relations']['delete'])) {
|
| 259 |
foreach ($_SESSION['relations']['delete'] as $nid => $title) {
|
| 260 |
relations_api_delete($node->nid, $nid);
|
| 261 |
}
|
| 262 |
}
|
| 263 |
if (!empty($_SESSION['relations']['create'])) {
|
| 264 |
foreach ($_SESSION['relations']['create'] as $nid => $title) {
|
| 265 |
relations_api_create($node->nid, $nid);
|
| 266 |
}
|
| 267 |
}
|
| 268 |
unset($_SESSION['relations']);
|
| 269 |
if (isset($node->relations_blocks)) {
|
| 270 |
$node->relations_blocks = array_filter($node->relations_blocks, 'is_string');
|
| 271 |
variable_set('relations_blocks_node_'. $node->nid, $node->relations_blocks);
|
| 272 |
}
|
| 273 |
break;
|
| 274 |
case 'delete':
|
| 275 |
variable_del('relations_blocks_node_'. $node->nid);
|
| 276 |
break;
|
| 277 |
}
|
| 278 |
}
|
| 279 |
}
|
| 280 |
|
| 281 |
//////////////////////////////////////////////////////////////////////////////
|
| 282 |
// Picker API hooks
|
| 283 |
|
| 284 |
function relations_picker_access($node2) {
|
| 285 |
// When adding relations to an existing node:
|
| 286 |
if (!empty($_SERVER['HTTP_REFERER']) && preg_match('!node/(\d+)/!', $_SERVER['HTTP_REFERER'], $matches)) {
|
| 287 |
$node1 = node_load((int)$matches[1]);
|
| 288 |
return relations_allowed($node1, $node2);
|
| 289 |
}
|
| 290 |
// When adding relations to a node to be created:
|
| 291 |
if (!empty($_SESSION['relations'])) {
|
| 292 |
return relations_allowed(TRUE, $node2);
|
| 293 |
}
|
| 294 |
}
|
| 295 |
|
| 296 |
//////////////////////////////////////////////////////////////////////////////
|
| 297 |
// Relations API implementation
|
| 298 |
|
| 299 |
function relations_menu_access($node, $perm) {
|
| 300 |
return user_access($perm) && (empty($node) || relations_is_node_type_enabled($node->type));
|
| 301 |
}
|
| 302 |
|
| 303 |
function relations_is_node_type_enabled($type) {
|
| 304 |
return array_search($type, relations_get_node_types());
|
| 305 |
}
|
| 306 |
|
| 307 |
function relations_get_node_types($node = NULL) {
|
| 308 |
return isset($node->nid) ?
|
| 309 |
variable_get('relations_blocks_node_'. $node->nid, relations_get_node_types(NULL)) :
|
| 310 |
array_filter(variable_get('relations_node_types', array()), 'is_string');
|
| 311 |
}
|
| 312 |
|
| 313 |
function relations_get_predicate() {
|
| 314 |
return variable_get('relations_predicate', RELATIONS_PREDICATE);
|
| 315 |
}
|
| 316 |
|
| 317 |
function relations_get_predicates($op = NULL) {
|
| 318 |
switch ($op) {
|
| 319 |
case 'keys':
|
| 320 |
return array_combine(array_keys(relations_get_predicates()), array_fill(0, count(relations_get_predicates()), ''));
|
| 321 |
default:
|
| 322 |
return array(
|
| 323 |
relations_get_predicate() => (object)array('title' => t('See also'), 'uri' => relations_get_predicate(), 'bidirectional' => TRUE),
|
| 324 |
);
|
| 325 |
}
|
| 326 |
}
|
| 327 |
|
| 328 |
function relations_rdf_options() {
|
| 329 |
return array('repository' => RELATIONS_REPOSITORY);
|
| 330 |
}
|
| 331 |
|
| 332 |
function relations_node_url($nid) {
|
| 333 |
return url('node/'. $nid, array('alias' => TRUE, 'absolute' => TRUE));
|
| 334 |
}
|
| 335 |
|
| 336 |
function relations_api_query($url) {
|
| 337 |
// TODO: this breaks encapsulation; should probably implement multi-queries directly in RDF API.
|
| 338 |
return array_merge(
|
| 339 |
rdf_query(rdf_uri($url), NULL, NULL, relations_rdf_options())->to_array(),
|
| 340 |
rdf_query(NULL, NULL, rdf_uri($url), relations_rdf_options())->to_array());
|
| 341 |
}
|
| 342 |
|
| 343 |
function relations_api_lookup($node, array $options = array()) {
|
| 344 |
global $user;
|
| 345 |
$account = empty($options['user']) ? $user : (is_object($options['user']) ? $options['user'] : user_load($options['user']));
|
| 346 |
$nid = is_object($node) ? $node->nid : (int)$node;
|
| 347 |
|
| 348 |
// Support for filtering related nodes by one or more content types:
|
| 349 |
if (!empty($options['type'])) {
|
| 350 |
$options['type'] = is_array($options['type']) ? $options['type'] : array($options['type']);
|
| 351 |
}
|
| 352 |
|
| 353 |
$nodes = array();
|
| 354 |
$results = relations_api_query(relations_node_url($nid));
|
| 355 |
foreach ($results as $triple) {
|
| 356 |
list($s, $p, $o) = $triple;
|
| 357 |
if ((preg_match('!node/'. $nid .'$!', (string)$s) && preg_match('!node/(\d+)$!', (string)$o, $matches)) ||
|
| 358 |
(preg_match('!node/'. $nid .'$!', (string)$o) && preg_match('!node/(\d+)$!', (string)$s, $matches))) {
|
| 359 |
if (($node = node_load($matches[1])) && node_access('view', $node, $account)) {
|
| 360 |
if (empty($options['type']) || in_array($node->type, $options['type'])) {
|
| 361 |
$nodes[$node->nid] = $node;
|
| 362 |
}
|
| 363 |
}
|
| 364 |
}
|
| 365 |
}
|
| 366 |
return $nodes;
|
| 367 |
}
|
| 368 |
|
| 369 |
function relations_api_create($node1, $node2, array $options = array()) {
|
| 370 |
$nid1 = is_object($node1) ? $node1->nid : (int)$node1;
|
| 371 |
$nid2 = is_object($node2) ? $node2->nid : (int)$node2;
|
| 372 |
|
| 373 |
// Make sure we never insert duplicate statements:
|
| 374 |
if (($existing = relations_api_lookup($nid1, $options)) && isset($existing[$nid2])) {
|
| 375 |
return TRUE;
|
| 376 |
}
|
| 377 |
|
| 378 |
$url1 = rdf_uri(relations_node_url($nid1));
|
| 379 |
$url2 = rdf_uri(relations_node_url($nid2));
|
| 380 |
return rdf_insert($url1, relations_get_predicate(), $url2, relations_rdf_options());
|
| 381 |
}
|
| 382 |
|
| 383 |
function relations_api_delete($node1, $node2, array $options = array()) {
|
| 384 |
$nid1 = is_object($node1) ? $node1->nid : (int)$node1;
|
| 385 |
$nid2 = is_object($node2) ? $node2->nid : (int)$node2;
|
| 386 |
|
| 387 |
$url1 = rdf_uri(relations_node_url($nid1));
|
| 388 |
$url2 = rdf_uri(relations_node_url($nid2));
|
| 389 |
rdf_delete($url1, relations_get_predicate(), $url2, relations_rdf_options());
|
| 390 |
rdf_delete($url2, relations_get_predicate(), $url1, relations_rdf_options());
|
| 391 |
return TRUE;
|
| 392 |
}
|
| 393 |
|
| 394 |
function relations_allowed($node1, $node2) {
|
| 395 |
// Must have edit access to either the source or destination node, and
|
| 396 |
// view access to the target node (view access to the source node is
|
| 397 |
// implied):
|
| 398 |
$editor1 = is_object($node1) ? node_access('update', $node1) : (bool)$node1;
|
| 399 |
$editor2 = node_access('update', $node2);
|
| 400 |
$reader2 = node_access('view', $node2);
|
| 401 |
return ($editor1 || $editor2) && $reader2;
|
| 402 |
}
|