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

Contents of /contributions/modules/relativity/relativity.module

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


Revision 1.49 - (show annotations) (download) (as text)
Sat Jan 12 02:27:19 2008 UTC (22 months, 1 week ago) by darius
Branch: MAIN
CVS Tags: HEAD
Changes since 1.48: +23 -13 lines
File MIME type: text/x-php
Fixing issue 165524 regarding regular settings
1 <?php
2 // $Id: relativity.module,v 1.48 2007/10/24 17:45:32 darius Exp $
3
4 /**
5 * @file
6 * This module allows you to attach nodes to other nodes.
7 *
8 * To store this extra information, we need an auxiliary database table.
9 */
10
11 if (module_exists('views')) {
12 include_once('relativity_views.inc');
13 }
14
15 /**
16 * Generates an array of named nodes keyed by node type.
17 * With no args, returns relativity node types
18 */
19 function relativity_node_list($use_blank='', $show_all=0) {
20 $node_list = array();
21
22 if ($show_all) {
23 $node_list = node_get_types('names');
24 }
25 else {
26 $show_nodes = variable_get('relativity_allow_types', array());
27 foreach($show_nodes as $key=>$type) {
28 if ($type != 'default') {
29 $node_list[$type] = node_get_types('name',$type);
30 }
31 }
32 }
33
34 if ($use_blank) {
35 $node_list['default'] = t($use_blank);
36 }
37
38 return $node_list;
39 }
40
41 /**
42 * Lists nodes that could be attached to a given parent type.
43 *
44 * TODO:
45 * Alter this code so that node_access("view",$child) logic is impelemented in the SQL statement.
46 * Use pager_query to process the results. Maybe even have a sortable table
47 * with various filters for restricting what is seen. Someday, maybe :)
48 * javanaut
49 */
50 function relativity_list_possible_children($type='', $parent_nid='') {
51 if (!$type && !$parent_nid) {
52 $type = arg(2);
53 $parent_nid = is_numeric(arg(4)) ? arg(4) : $_GET['parent_node'] + 0;
54 }
55
56 $links = array();
57 $parent = node_load($parent_nid);
58 $relativity_query = variable_get('relativity_child_query_'.$parent->type.'_'.$type, NULL);
59 $common_children_reqd = variable_get('relativity_common_child_'.$parent->type.'_'.$type, array());
60 if ($relativity_query && $query = node_load($relativity_query)) {
61 //drupal_set_message("executing query ".print_r($query,1));
62 foreach(relativity_execute_query($query, NULL, $parent_nid) as $chnid => $child) {
63 if ($child->type == $type) {
64 $links[] = theme('relativity_link', $child->title, "addparent/$child->nid/parent/$parent_nid", $parent_nid, ' '.theme_relativity_trace($child->relativity_trace));
65 }
66 }
67 }
68 elseif (count($common_children_reqd)) {
69 $otherparents = relativity_list_grand_relatives($parent, 'child', 'parent', $common_children_reqd, array($type));
70
71 foreach($otherparents as $childparent) {
72 if (node_access("view",$childparent)) {
73 $links[] = theme('relativity_link', $childparent->title, "addparent/$childparent->nid/parent/$parent_nid", $parent_nid, 'common child required');
74 }
75 }
76 }
77 else {
78 // So, look up all valid nids attached to this parent_node already..
79 $result = db_query('SELECT n.nid FROM {node} n LEFT OUTER JOIN {relativity} r ON n.nid=r.nid WHERE n.type = \'%s\' AND r.parent_nid=%d', $type, $parent_nid);
80 $excluded_nids = array($parent_nid);
81 // The number of nids returned by this array should be reasonably small.
82 // It's the number of nodes attached to a given node already.
83 while($existing_nid = db_fetch_object($result)) {
84 $excluded_nids[] = $existing_nid->nid;
85 }
86
87 $use_taxonomy = variable_get('relativity_use_taxonomy', 0);
88 if ($use_taxonomy) {
89 $result = db_query('SELECT DISTINCT n.nid, t.tid, v.vid FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid INNER JOIN {term_data} v ON t.tid = v.tid WHERE n.type = \'%s\' AND n.status = 1 ORDER BY v.vid, t.tid', $type);
90 } else {
91 $result = db_query('SELECT n.nid FROM {node} n WHERE n.type = \'%s\' AND n.status = 1 ORDER BY n.title', $type);
92 }
93 }
94
95 while($child = db_fetch_object($result)) {
96 if (in_array($child->nid, $excluded_nids)) { continue; }
97 $child_node = node_load($child->nid);
98 if (node_access('view', $child_node)) {
99 if ($use_taxonomy) {
100 $parent_tree = array_reverse(taxonomy_get_parents_all($child->tid));
101 // Building the array key
102 for ($i=0; $i<count($parent_tree); $i++) {
103 if($i==0) {
104 $key = $parent_tree[$i]->vid.';'.$parent_tree[$i]->tid.';';
105 } else {
106 $term = taxonomy_get_term($parent_tree[$i]->tid);
107 $key .= $parent_tree[$i]->tid.';';
108 }
109 }
110 $links[$key][] = array('title' => $child_node->title, 'child_node' => $child_node->nid, 'parent_node' => $parent_nid);
111 } else {
112 $links[] = theme('relativity_link', $child_node->title, "addparent/$child_node->nid/parent/$parent_nid", $parent_nid);
113 }
114 }
115 }
116
117 if (count($links)) {
118 if ($use_taxonomy) {
119 print(theme('page', drupal_get_form('relativity_taxonomy_form', $links) ));
120 } else {
121 print(theme('page', theme('relativity_bullets', $links)));
122 }
123 } else {
124 drupal_set_message(t('There are no available !s items to attach', array('!s' => node_get_types('name',$type))));
125 drupal_goto("node/$parent_nid");
126 }
127 }
128
129 /**
130 * Lists all grandparents or grandchildren of the given node.
131 * Additionally, allow the direction of search to change directions by having different
132 * values for $rel_type1 and $rel_type2.
133 * @param $node the node to start searching from
134 * @param $rel_type1 Which direction to look ('parent' or 'child')
135 * @param $rel_type2 Which direction to look beyond children/parents that were found (grand)('parent' or 'child')
136 * @param $conduit_types array of connecting node types to restrict the search to
137 * @param $types array of grandparent/grandchild node types to restrict the results to
138 * @return array of grandparent/grandchild nodes that were found
139 */
140 function relativity_list_grand_relatives($node, $rel_type1='child', $rel_type2='child', $conduit_types = NULL, $types = NULL, $exclude_circular_paths=TRUE) {
141 //$nodes = array();
142
143 // List all children/parents. Restrict list to $conduit_types if specified
144 $conduit_types_sql = "";
145 if (is_array($conduit_types) && count($conduit_types)) {
146 $conduit_types_sql = " AND n.type IN ('".implode("','",$conduit_types)."') ";
147 }
148
149 if ($rel_type1 == 'parent') {
150 $result = db_query("SELECT DISTINCT n.nid as nid FROM {node} n INNER JOIN {relativity} r ON n.nid=r.parent_nid WHERE r.nid=%d $conduit_types_sql", $node->nid);
151 }
152 else {
153 $result = db_query("SELECT DISTINCT n.nid as nid FROM {node} n INNER JOIN {relativity} r ON n.nid=r.nid WHERE r.parent_nid=%d $conduit_types_sql", $node->nid);
154 }
155
156 while($obj = db_fetch_object($result)) {
157 $relatives[$obj->nid] = $obj->nid;
158 }
159
160 if (is_array($relatives) && count($relatives)) {
161 // list all children/parents of the $relatives found. Restrict list to $conduit_types if specified
162
163 // identify parents and children for exclusion
164 if ($exclude_circular_paths) {
165 $all_relatives[$node->nid] = $node->nid; // exclude self
166
167 // parents
168 $result = db_query("SELECT DISTINCT n.nid as nid FROM {node} n INNER JOIN {relativity} r ON n.nid=r.parent_nid WHERE r.nid = %d", $node->nid);
169 while($obj = db_fetch_object($result)) {
170 $all_relatives[$obj->nid] = $obj->nid;
171 }
172
173 // children
174 $result = db_query("SELECT DISTINCT n.nid as nid FROM {node} n INNER JOIN {relativity} r ON n.nid=r.nid WHERE r.parent_nid = %d", $node->nid);
175 while($obj = db_fetch_object($result)) {
176 $all_relatives[$obj->nid] = $obj->nid;
177 }
178 }
179 $all_relatives = is_array($all_relatives) ? $all_relatives : array();
180
181 $types_sql = "";
182 if (is_array($types) && count($types)) {
183 $types_sql = " AND n.type IN ('".implode("','",$types)."') ";
184 }
185
186 if ($rel_type2 == 'parent') {
187 $result = db_query("SELECT DISTINCT n.nid as nid FROM {node} n INNER JOIN {relativity} r ON n.nid=r.parent_nid WHERE r.nid IN (".implode(",",$relatives).") $types_sql");
188 }
189 else {
190 $result = db_query("SELECT DISTINCT n.nid as nid FROM {node} n INNER JOIN {relativity} r ON n.nid=r.nid WHERE r.parent_nid IN (".implode(",",$relatives).") $types_sql");
191 }
192
193 while($obj = db_fetch_object($result)) {
194 if (!($exclude_circular_paths && isset($all_relatives[$obj->nid]))) {
195 $grand_relatives[$obj->nid] = node_load($obj->nid);
196 }
197 }
198 }
199
200 return is_array($grand_relatives) ? $grand_relatives : array();
201 }
202
203 /**
204 * Delete the parent/node relationship
205 */
206 function relativity_unparent_node() {
207 $output = "";
208 $parent_nid = arg(2);
209 $child_nid = arg(3);
210 if (is_numeric($parent_nid) && is_numeric($child_nid)) {
211 $parent = node_load($parent_nid);
212 $child = node_load($child_nid);
213 if (relativity_may_unchild($parent, $child)) {
214 $result = db_query('DELETE FROM {relativity} WHERE nid = %d AND parent_nid = %d', $child_nid, $parent_nid);
215 drupal_set_message(t('Node relationship removed.'));
216 }
217 else {
218 drupal_set_message(t('Node relationship cannot currently be removed.'));
219 }
220 }
221 else {
222 drupal_set_message(t('Either that node does not exist or you don\'t have proper privileges to update it'));
223 }
224 drupal_goto('node/'.$parent_nid);
225 }
226
227 /**
228 * Attach a node to its parent
229 */
230 function relativity_addparent($child_nid="", $parent_nid="") {
231 if (!$child_nid && !$parent_nid) {
232 $child_nid = arg(2);
233 $parent_nid = arg(4);
234 }
235 $output = "";
236
237 db_query('INSERT INTO {relativity} (nid, parent_nid) VALUES (%d, %d)', $child_nid, $parent_nid);
238 drupal_set_message(t('Node relationship created.'));
239 drupal_goto('node/'.$parent_nid);
240 }
241
242 /**
243 * Attach multiple nodes to its parent
244 */
245 function relativity_addparent_multiple() {
246 $edit = $_POST['edit'];
247 $parent_nid = $_POST['parent_nid'];
248 if ($edit['child_nids']) {
249 $child_nids = array_keys($edit['child_nids']);
250 for ($i=0; $i<count($child_nids); $i++) {
251 db_query('INSERT INTO {relativity} (nid, parent_nid) VALUES (%d, %d)', $child_nids[$i], $parent_nid);
252 }
253 drupal_set_message(t('Node relationship created.'));
254 drupal_goto('node/'.$parent_nid);
255 } else {
256 drupal_set_message(t('You did not select a node!'));
257 drupal_goto('node/'.$parent_nid);
258 }
259 }
260
261 function relativity_menu($may_cache) {
262 global $_menu;// = menu_get_menu();
263 $items = array();
264
265 if ($may_cache && variable_get('relativity_enforce_parent_rules', FALSE)) {
266 // * not working how I wanted it to
267 // * This needs node_add in node.module to check menu before displaying links
268
269 // iterate through all node types and take over their node/add handlers
270 foreach(relativity_node_list() as $type){
271 if (relativity_requires_parent($type)) {
272 //drupal_set_message("setting access for type $type");
273 $items[] = array('path' => 'node/add/'. $type, 'title' => t('content type requires parent'),
274 'callback' => 'node_add',
275 'callback arguments' => array($type),
276 'access' => 0,//user_access('administer content'),
277 'type' => MENU_CALLBACK,
278 'priority' => 1,
279 'weight' => 1
280 );
281 }
282 }
283 }
284
285 if (!$may_cache) {
286 if (arg(0) == 'node' && arg(1) == 'add' && array_key_exists(arg(2),relativity_node_list()) && arg(3) == 'parent' && is_numeric(arg(4))) {
287 // this is my crafty way of creating new nodes as children of parents, and
288 // not displaying any links to create them otherwise.
289 $_GET['parent_node'] = arg(4);
290 $items[] = array('path' => 'node/add/'. arg(2).'/parent/'.arg(4), 'title' => t('create content'),
291 'callback' => 'node_add',
292 'callback arguments' => array(arg(2)),
293 'access' => node_access('create', arg(2)),
294 'type' => MENU_CALLBACK,
295 'weight' => 1
296 );
297 }
298 elseif (arg(0) == 'relativity' && arg(1) == 'listnodes' && array_key_exists(arg(2),relativity_node_list()) && arg(3) == 'parent' && is_numeric(arg(4))) {
299
300 $items[] = array('path' => 'relativity/listnodes/'. arg(2).'/parent/'.arg(4), 'title' => t('list of !type nodes to attach', array('!type'=>node_get_types('name',arg(2)))),
301 'callback' => 'relativity_list_possible_children',
302 'access' => node_access('update', node_load(arg(4))) && user_access("access content"),
303 'type' => MENU_CALLBACK
304 );
305 }
306 elseif (arg(0) == 'relativity' && arg(1) == 'addparent' && is_numeric(arg(2)) && arg(3) == "parent" && is_numeric(arg(4))) {
307 $items[] = array('path' => 'relativity/addparent/'. arg(2) .'/parent/'. arg(4), 'title' => t('attach node to parent'),
308 'callback' => 'relativity_addparent',
309 'access' => node_access("view", node_load(arg(2))),
310 'type' => MENU_CALLBACK
311 );
312 }
313 elseif (arg(0) == 'relativity' && arg(1) == 'addparent' && arg(2) == 'multiple') {
314 $items[] = array('path' => 'relativity/addparent/multiple', 'title' => t('attach node to parent'),
315 'callback' => 'relativity_addparent_multiple',
316 'access' => node_access('view'),
317 'type' => MENU_CALLBACK
318 );
319 }
320 elseif (arg(0) == 'relativity' && arg(1) == 'unparent' && is_numeric(arg(2)) && is_numeric(arg(3))) {
321 $items[] = array('path' => 'relativity/unparent/'.arg(2).'/'.arg(3), 'title' => t('unparent node'),
322 'callback' => 'relativity_unparent_node',
323 'access' => node_access("update", node_load(arg(2))),
324 'type' => MENU_CALLBACK
325 );
326 }
327 }
328 else {
329 $items[] = array(
330 'path' => 'admin/settings/relativity',
331 'title' => t('Node relativity'),
332 'description' => t('Node relativity settings.'),
333 'callback' => 'drupal_get_form',
334 'callback arguments' => array('relativity_regular_settings'),
335 'access' => user_access('administer site configuration'),
336 'type' => MENU_NORMAL_ITEM, // optional
337 );
338
339 $items[] = array('path' => 'admin/settings/relativity/regular',
340 'title' => t('Regular settings'),
341 'type' => MENU_DEFAULT_LOCAL_TASK,
342 'weight' => -10
343 );
344
345 $items[] = array(
346 'path' => 'admin/settings/relativity/display',
347 'title' => t('Display settings'),
348 'callback' => 'drupal_get_form',
349 'callback arguments' => array('relativity_display_settings'),
350 'access' => user_access('administer site configuration'),
351 'type' => MENU_LOCAL_TASK,
352 );
353
354 $items[] = array(
355 'path' => 'admin/settings/relativity/advanced',
356 'title' => t('Advanced settings'),
357 'callback' => 'drupal_get_form',
358 'callback arguments' => array('relativity_advanced_settings'),
359 'access' => user_access('administer site configuration'),
360 'type' => MENU_LOCAL_TASK,
361 );
362 }
363 return $items;
364 }
365
366 function relativity_regular_settings() {
367 $all_types = relativity_node_list('', TRUE);
368 $relativity_types = relativity_node_list('', FALSE);
369
370 $group['description'] = array(
371 '#type' => 'item',
372 '#title' => '',
373 '#value' => t('Below are the general settings for node relationships for each node type. The !advanced and !display pages use the settings on this page, so be sure to save these settings before proceeding to either of them.', array('!display'=>l('Display settings', 'admin/settings/relativity/display'), '!advanced'=>l('Advanced settings', 'admin/settings/relativity/advanced'))),
374 );
375
376 $group['global_options'] = array(
377 '#type' => 'fieldset',
378 '#title' => t('Global Options'),
379 );
380
381 $group['global_options']['relativity_allow_types'] = array(
382 '#type' => 'select',
383 '#title' => t('Node types that can be involved in relationships'),
384 '#default_value' => variable_get('relativity_allow_types', 'default'),
385 '#options' => relativity_node_list('',TRUE),
386 '#description' => t('What types of nodes should Node Relativity use? Be sure to include any node type involved (both parent and children types). You can use Ctrl+Click to unselect an option. After saving this, configuration options will appear for all of the node types that you specified. After selecting appropriate settings for each node type, please save again.'),
387 '#size' => count(relativity_node_list('',TRUE)),
388 '#multiple' => TRUE,
389 );
390
391
392 // set to default values if type not selected
393 foreach ($all_types as $type=>$name) {
394 if (!in_array($type, array_keys($relativity_types))) { // type is not in relativity_types
395 variable_set('relativity_parent_ord_'.$type, 'any');
396 variable_set('relativity_type_'.$type, array());
397 }
398 }
399
400 // no node types to manage
401 if (count($relativity_types) == 0) {
402 return system_settings_form($group);
403 }
404
405 $group['global_options']['relativity_ancestors_label'] = array(
406 '#type' => 'textfield',
407 '#title' => t('Label for "Ancestor nodes"'),
408 '#default_value' => variable_get('relativity_ancestors_label', t('Ancestor nodes')),
409 '#size' => 60,
410 '#maxlength' => 250,
411 );
412
413 $group['global_options']['relativity_parents_label'] = array(
414 '#type' => 'textfield',
415 '#title' => t('Label for "Parent nodes"'),
416 '#default_value' => variable_get('relativity_parents_label', t('Parent nodes')),
417 '#size' => 60,
418 '#maxlength' => 250,
419 );
420
421 $group['global_options']['relativity_children_label'] = array(
422 '#type' => 'textfield',
423 '#title' => t('Label for "Children nodes"'),
424 '#default_value' => variable_get('relativity_children_label', t('Children nodes')),
425 '#size' => 60,
426 '#maxlength' => 250,
427 );
428
429 $group['global_options']['relativity_actions_label'] = array(
430 '#type' => 'textfield',
431 '#title' => t('Label for "Link operations"'),
432 '#default_value' => variable_get('relativity_actions_label', t('Link operations')),
433 '#size' => 60,
434 '#maxlength' => 250,
435 );
436
437
438 $collapsed = (count($relativity_types) == 1) ? FALSE : TRUE;
439 foreach ($relativity_types as $type=>$name) {
440 $group['node_'.$type.'_options'] = array(
441 '#type' => 'fieldset',
442 '#collapsible' => TRUE,
443 '#collapsed' => $collapsed,
444 '#title' => t('Options for !name (!type) nodes', array('!name'=>$name, '!type' =>$type)),
445 );
446
447 $group['node_'.$type.'_options']['relativity_parent_ord_'.$type] = array(
448 '#type' => 'select',
449 '#title' => t('Parental Ordinality'),
450 '#default_value' => variable_get('relativity_parent_ord_'.$type, 'any'),
451 '#options' => array('any'=>t('any'), 'one'=>t('one'), 'none'=>t('none'), 'one or more'=>t('one or more')),
452 '#description' => t('How many parents can this node type have?'),
453 );
454
455 $group['node_'.$type.'_options']['relativity_type_'.$type] = array(
456 '#type' => 'select',
457 '#title' => t('Allowable Child Node types'),
458 '#default_value' => variable_get('relativity_type_'.$type, array()),
459 '#options' => relativity_node_list('',FALSE),
460 '#description' => t('What types of nodes are allowed to be attached to this type?'),
461 '#size' => count(relativity_node_list('',FALSE)),
462 '#multiple' => TRUE,
463 );
464 }
465
466 return system_settings_form($group);
467 }
468
469 function relativity_advanced_settings() {
470 $allow_types = relativity_node_list();
471
472 $group['global_options'] = array(
473 '#type' => 'fieldset',
474 '#title' => t('Global Options'),
475 );
476
477 $group['global_options']['relativity_enforce_parent_rules'] = array(
478 '#type' => 'checkbox',
479 '#title' => t('Enforce Parental Rules'),
480 '#return_value' => 1,
481 '#default_value' => variable_get('relativity_enforce_parent_rules', 0),
482 '#description' => t('If checked, nodes cannot be created without a parent if they require a parent to exist.'),
483 );
484
485 // Node Sorting Options
486 $group['sorting_options'] = array(
487 '#type' => 'fieldset',
488 '#title' => t('Node Sorting Options'),
489 );
490
491 $group['global_options']['relativity_use_taxonomy'] = array(
492 '#type' => 'checkbox',
493 '#title' => t('Use Taxonomy to attach nodes'),
494 '#return_value' => 1,
495 '#default_value' => variable_get('relativity_use_taxonomy', 0),
496 '#description' => t('If checked, nodes cannot be attached if they don\'t belong to a taxonomy term. When listing nodes to attach, possible children will be displayed in a taxonomy tree.'),
497 );
498
499 // let admins specify sort order for node types
500 $ntypes = count($allow_types);
501 foreach($allow_types as $type => $name) {
502 $node_order[$type] = variable_get('relativity_node_order_'.$type, -1);
503 if (($node_order[$type] < 1) || ($node_order[$type] > $ntypes)) {
504 $node_order[$type] = 1; // default to 1
505 }
506 }
507
508 // we need a keyed array where numbers are sort order
509 $sort_options = range(1, $ntypes);
510 foreach($sort_options as $opt) {
511 $options[$opt] = $opt;
512 }
513
514 // display list of node types sorted by ascending sort order
515 for($i=1; $i<=$ntypes; $i++) {
516 foreach($allow_types as $type => $name) {
517 if ($node_order[$type] == $i) {
518 $group['sorting_options']['relativity_node_order_'.$type] = array(
519 '#type' => 'select',
520 '#title' => t('Sort Order for !name Nodes', array('!name'=>$name)),
521 '#default_value' => variable_get('relativity_node_order_'.$type, ""),
522 '#options' => $options,
523 );
524 }
525 }
526 }
527
528 $collapsed = (count(relativity_node_list()) == 1) ? FALSE : TRUE;
529 foreach(relativity_node_list() as $type=>$name) {
530 $group['node_'.$type.'_options'] = array(
531 '#type' => 'fieldset',
532 '#collapsible' => TRUE,
533 '#collapsed' => $collapsed,
534 '#title' => t('!label Options for !name (!type) nodes', array('!name'=>$name, '!type' =>$type, '!label'=>$label[$tab])),
535 );
536
537 foreach (relativity_node_list() as $chtype=>$chname) {
538 if (in_array($chtype, variable_get('relativity_type_'.$type, array()))) {
539 $group['node_'.$type.'_options']['relativity_child_ord_'.$type.'_'.$chtype] = array(
540 '#type' => 'select',
541 '#title' => t('!chname Child Ordinality for !pname Parents', array('!chname'=>$chname, '!pname'=>$name)),
542 '#default_value' => variable_get('relativity_child_ord_'.$type.'_'.$chtype, 'any'),
543 '#options' => array('any'=>t('any'), '1'=>t('1'), '2'=>t('2'), '3'=>t('3'), '4'=>t('4'), '5'=>t('5'), '6'=>t('6'), '7'=>t('7'), '8'=>t('8'), '9'=>t('9'), '10'=>t('10')),
544 );
545
546
547 // "Require Common Child of specified type" feature
548 // find the intersection of 'relativity_type_'.$type and 'relativity_type_'.$chtype (common allowable child types for parent and child)
549 $common_child_types = array_intersect(variable_get('relativity_type_'.$type, array()), variable_get('relativity_type_'.$chtype, array()));
550 if (count($common_child_types)) {
551 foreach($common_child_types as $cchtype) {
552 $common_types[$cchtype] = node_get_types('name',$cchtype);
553 }
554 $group['node_'.$type.'_options']['relativity_common_child_'.$type.'_'.$chtype] = array(
555 '#type' => 'select',
556 '#title' => t('Require Common Child Node Types for !chname Children', array('!chname'=>$chname)),
557 '#default_value' => variable_get('relativity_common_child_'.$type.'_'.$chtype, array()),
558 '#options' => $common_types,
559 '#description' => t('Require that any child of this type already have a child in common of the specified type. This allows particular circular relationships to be created.'),
560 '#extra' => 0,
561 '#multiple' => TRUE,
562 );
563 }
564
565 // list out possible relativity_queries that could be used to define this relationship type
566 $possible_queries = NULL;
567 // NOTE: This query depends on PHP's formatting of serialized arrays. If the implementation changes, this will need to be updated. Yes, it's a cheap hack ;)
568 $sql = "SELECT n.nid, n.title FROM {node} n INNER JOIN {relativity_query} r ON r.nid=n.nid WHERE n.type='relativity' AND r.search_types LIKE '%\"$chtype\"%'";
569 $result = db_query($sql);
570 while($obj = db_fetch_object($result)) {
571 $possible_queries[$obj->nid] = $obj->title;
572 }
573 if (is_array($possible_queries) && count($possible_queries)) {
574 $possible_queries[0] = t('none');
575 //drupal_set_message($sql." found possible queries: ".print_r($possible_queries,1));
576 $group['node_'.$type.'_options']['relativity_child_query_'.$type.'_'.$chtype] = array(
577 '#type' => 'select',
578 '#title' => t('Use Relativity Query For Listing !chname Children', array('!chname'=>$chname)),
579 '#default_value' => variable_get('relativity_child_query_'.$type.'_'.$chtype, ''),
580 '#options' => $possible_queries,
581 '#description' => t('Use the specified relativity query to identify possible children.'),
582 );
583 }
584 }
585 }
586 }
587 return system_settings_form($group);
588 }
589
590 function relativity_display_settings() {
591
592 $collapsed = (count(relativity_node_list()) == 1) ? FALSE : TRUE;
593 foreach(relativity_node_list() as $type=>$name) {
594 $group['node_'.$type.'_options'] = array(
595 '#type' => 'fieldset',
596 '#collapsible' => TRUE,
597 '#collapsed' => $collapsed,
598 '#description' => 'Here you can enter weights (positive or negative, integer or decimal) that determine where in the node body relativity links are displayed. Usually, the body has weight 0. For example, if you want Children Node links to appear at the top, enter -10.',
599 '#title' => t('Display options for !name (!type) nodes', array('!name'=>$name, '!type' =>$type)),
600 );
601
602 $group['node_'.$type.'_options']['relativity_'.$type.'_ancestor_weight'] = array(
603 '#type' => 'textfield',
604 '#title' => t('Ancestor nodes weight'),
605 '#description' => t('Enter 0 for no display'),
606 '#size' => 4,
607 '#maxlength' => 4,
608 '#default_value' => variable_get('relativity_'.$type.'_ancestor_weight', 0),
609 );
610
611 $group['node_'.$type.'_options']['relativity_'.$type.'_parents_weight'] = array(
612 '#type' => 'textfield',
613 '#title' => t('Parent nodes weight'),
614 '#description' => t('Enter 0 for no display'),
615 '#size' => 4,
616 '#maxlength' => 4,
617 '#default_value' => variable_get('relativity_'.$type.'_parents_weight', 10),
618 );
619
620 $group['node_'.$type.'_options']['relativity_'.$type.'_children_weight'] = array(
621 '#type' => 'textfield',
622 '#title' => t('Children nodes weight'),
623 '#description' => t('Enter 0 for no display'),
624 '#size' => 4,
625 '#maxlength' => 4,
626 '#default_value' => variable_get('relativity_'.$type.'_children_weight', 11),
627 );
628
629 $group['node_'.$type.'_options']['relativity_'.$type.'_operations_weight'] = array(
630 '#type' => 'textfield',
631 '#title' => t('Link operations weight'),
632 '#description' => t('Enter 0 for no display'),
633 '#size' => 4,
634 '#maxlength' => 4,
635 '#default_value' => variable_get('relativity_'.$type.'_operations_weight', 12),
636 );
637
638
639 $render_opts = array(
640 'title' => t('title only'),
641 'teaser' => t('node teaser'),
642 'body' => t('node body'),
643 'hide' => t('hide this child type')
644 );
645
646 // allow user to choose a view to render a specifc
647 // child type
648 if (module_exists('views')) {
649 $views = array();
650 $result = db_query("SELECT name FROM {view_view} ORDER BY name");
651 while ($view = db_fetch_array($result)) {
652 $views[t('Existing Views')]['view:'.$view['name']] = $view['name'];
653 }
654 views_load_cache();
655 $default_views = _views_get_default_views();
656 foreach ($default_views as $view) {
657 $views[t('Default Views')]['view:'.$view->name] = $view->name;
658 }
659 $render_opts = array_merge($render_opts, $views);
660 }
661
662 foreach (relativity_node_list() as $chtype=>$chname) {
663 if (in_array($chtype, variable_get('relativity_type_'.$type, array()))) {
664 $group['node_'.$type.'_options']['relativity_render_'.$type.'_'.$chtype] = array(
665 '#type' => 'select',
666 '#title' => t('Rendering option for children nodes of type !chname', array('!chname'=>$chname)),
667 '#default_value' => variable_get('relativity_render_'.$type.'_'.$chtype, 'title'),
668 '#options' => $render_opts,
669 );
670 }
671 }
672 }
673 return system_settings_form($group);
674 }
675
676 /**
677 * Implementation of hook_block().
678 *
679 * Generates navigation block to quickly jump to any of this node's ancestors.
680 * See http://api.drupal.org/api/4.7/function/block_example_block
681 */
682 function relativity_block($op = 'list', $delta = 0, $edit = array()) {
683 // The $op parameter determines what piece of information is being requested.
684 switch ($op) {
685 case 'list':
686 // If $op is "list", we just need to return a list of block descriptions.
687 // This is used to provide a list of possible blocks to the administrator,
688 // end users will not see these descriptions.
689 $blocks[0]['info'] = t('Node relativity: ancestors');
690 $blocks[1]['info'] = t('Node relativity: children');
691 $blocks[2]['info'] = t('Node relativity: parent');
692 $blocks[3]['info'] = t('Node relativity: link operations');
693 return $blocks;
694
695 case 'configure':
696 // If $op is "configure", we need to provide the administrator with a
697 // configuration form. The $delta parameter tells us which block is being
698 // configured.
699 $form = array();
700 switch($delta) {
701 case 0: // Ancestors block
702 $form['relativity_nav_types'] = array(
703 '#type' => 'select',
704 '#title' => t('Allowable Relativity Block Node types'),
705 '#default_value' => variable_get('relativity_nav_types', relativity_node_list()),
706 '#options' => relativity_node_list('none'),
707 '#description' => t('What node types are allowed to be used in this block?'),
708 '#size' => 5,
709 '#multiple' => TRUE,
710 );
711 break;
712 }
713
714 return $form;
715
716 case 'save':
717 // If $op is "save", we need to save settings from the configuration form.
718 // Since the first block is the only one that allows configuration, we
719 // need to check $delta to make sure we only save it.
720 switch($delta) {
721 case 0;
722 variable_set('relativity_nav_types', $edit['relativity_nav_types']);
723 break;
724 }
725 return;
726
727 case 'view': default:
728 // If $op is "view", then we need to generate the block for display
729 // purposes. The $delta parameter tells us which block is being requested.
730
731 // see if we're viewing a node
732 if (arg(0) == 'node' && is_numeric(arg(1))) {
733 // see if it's a valid node
734 $node = node_load(arg(1));
735 if (is_object($node) && node_access('view', $node)) {
736 switch ($delta) {
737 case 0:
738 $ancestors = relativity_load_ancestors($node,1,TRUE);
739 if (is_array($ancestors) && count($ancestors) > 0) {
740 $block = array('subject' => variable_get('relativity_ancestors_label', t('Related Ancestors')),
741 'content' => theme('relativity_block_ancestors', $node, $ancestors),
742 );
743 }
744 break;
745 case 1:
746 $content = theme('relativity_block_children', $node);
747 if ($content) {
748 $block = array('subject' => variable_get('relativity_children_label', t('Related Items')),
749 'content' => $content,
750 );
751 }
752 break;
753 case 2:
754 $content = theme('relativity_block_parents', $node);
755 if ($content) {
756 $block = array('subject' => variable_get('relativity_parents_label', t('Related Parents')),
757 'content' => $content,
758 );
759 }
760 break;
761 case 3:
762 $content = theme('relativity_block_link_operations', $node);
763 if ($content) {
764 $block = array('subject' => variable_get('relativity_actions_label', t('Link operations')),
765 'content' => $content,
766 );
767 }
768 break;
769 }
770 }
771 }
772 return $block;
773 }
774 }
775
776 /**
777 * Generate an array of ancestors for the given node.
778 * This will keep looking for more ancestors until a circular path was found,
779 * if a node has multiple or no parents at all.
780 * If $load_multi_parent is true, the first multi-parent result found will be placed
781 * in the output array as an array of nodes. Otherwise, all array elements will be nodes.
782 */
783 function relativity_load_ancestors($node, $load_multi_parent=1, $block=FALSE) {
784 $search_max = 5; // make this a settings variable someday
785 if (is_numeric($node->nid)) {
786 $ancestors = array();
787 $nids = array();
788 $search_nid = $node->nid;
789 $cnt = 0;
790 $allowed_types = variable_get('relativity_allow_types', array());
791 if ($block == TRUE) {
792 $allowed_types = variable_get('relativity_nav_types', $allowed_types);
793 }
794
795 do {
796 $keep_going = 0;
797 $result = db_query("SELECT parent_nid from {relativity} where nid=%d", $search_nid);
798 $parent_count = db_num_rows($result);
799 if ($parent_count == 1 && $obj = db_fetch_object($result)) {
800 // make sure we're not pursuing a circular path and that we stop at $search_max parents
801 if (!in_array($obj->parent_nid, $nids) && $cnt++ < $search_max) {
802 //drupal_set_message("unshifting node ".$obj->parent_nid." onto the ancestors array");
803 $parent_node = node_load($obj->parent_nid);
804 if (node_access('view', $parent_node) && in_array($parent_node->type, $allowed_types)) {
805 array_unshift($ancestors, $parent_node);
806 $nids[] = $obj->parent_nid;
807 $search_nid = $obj->parent_nid;
808 $keep_going = 1;
809 }
810 }
811 }
812 elseif($parent_count > 1 && $load_multi_parent && $cnt++ < $search_max) {
813 //drupal_set_message("found parent_count == $parent_count, about to add array to ancestors array");
814 // perhaps upon encountering multiple parents, it would suffice to print them
815 // all out at the same level and not follow any of them up, but still allow
816 // the user to navigate up whatever hierarchy they want to.
817 $siblings = array();
818 while($obj = db_fetch_object($result)) {
819 // make an array of nodes to display at this level
820 $parent_node = node_load($obj->parent_nid);
821 if (!in_array($obj->parent_nid, $nids) && node_access('view', $parent_node) && in_array($parent_node->type, $allowed_types)) {
822 array_unshift($siblings, $parent_node);
823 $nids[] = $obj->parent_nid;
824 $search_nid = $obj->parent_nid; // only used when single result is rendered.
825 }
826 }
827 if (count($siblings) > 0) {
828 array_unshift($ancestors, $siblings);
829 }
830 if (count($siblings) == 1) {
831 $keep_going = 1;
832 }
833 }
834 } while($keep_going);
835 return $ancestors;
836 }
837 }
838
839 /**
840 * Determines if the specified parent node may add a child of type $type.
841 * @return true if possible, false otherwise.
842 */
843 function relativity_may_add_child($parent, $type) {
844 $common_children_reqd = variable_get('relativity_common_child_'.$parent->type.'_'.$type, array());
845 if (count($common_children_reqd)) {
846 // NOTE: all of this needs node_access logic applied at the SQL level. Currently, if unreadable relationships fill the need, they allow this to pass through
847
848 // lookup all existing children and see if any of them are on the common_children_reqd list
849 $result = db_query("SELECT n.nid as nid FROM {node} n INNER JOIN {relativity} r ON n.nid=r.nid WHERE r.parent_nid = %d AND n.type IN ('".implode("','", $common_children_reqd)."')", $parent->nid);
850 while($child = db_fetch_object($result)) {
851 $children[] = $child->nid;
852 }
853 // no common children defined for the parent, reject the request
854 if (!$children || !is_array($children) || !count($children)) {
855 return false;
856 }
857
858 // now, see if any of the children that were found have a parent of type $type
859 $children = implode(',', $children);
860 $result = db_query("SELECT count(n.nid) as cnt FROM {node} n INNER JOIN {relativity} r ON n.nid=r.parent_nid WHERE r.nid IN ($children) AND n.type='%s'", $type);
861 $count = db_result($result);
862 if (!$count) {
863 return false;
864 }
865 }
866
867 $ord = variable_get('relativity_child_ord_'.$parent->type.'_'.$type, 'any');
868 if ($ord == 'any') {
869 return true;
870 }
871 else {
872 // look up how many children this parent currently has of the specified type
873 $res = db_fetch_object(db_query("SELECT count(n.nid) as cnt FROM {node} n INNER JOIN {relativity} r ON n.nid=r.nid WHERE r.parent_nid = %d AND n.type='%s'", $parent->nid, $type));
874 if ($res->cnt >= $ord) {
875 return false;
876 }
877 else {
878 return true;
879 }
880 }
881 }
882
883 /**
884 * Convenience function for determining if a node requires a parent to exist.
885 */
886 function relativity_requires_parent($node) {
887 if (is_object($node)) {
888 $type = $node->type;
889 }
890 else {
891 $type = $node;
892 }
893 if (variable_get("relativity_parent_ord_$type", '') == 'one' ||
894 variable_get("relativity_parent_ord_$type", '') == 'one or more') {
895 return true;
896 }
897
898 return false;
899 }
900
901 /**
902 * Convenience function for determining if a node may have more than one parent.
903 */
904 function relativity_multi_parent($node) {
905 if (is_object($node)) {
906 $type = $node->type;
907 }
908 else {
909 $type = $node;
910 }
911 if (variable_get("relativity_parent_ord_$type", '') == 'any' ||
912 variable_get("relativity_parent_ord_$type", '') == 'one or more') {
913 return true;
914 }
915 return false;
916 }
917
918 /**
919 * If this node is required to connect a series of nodes together, return FALSE
920 */
921 function relativity_may_unchild($parent, $child) {
922
923 if (!node_access('update', $parent)) {
924 return FALSE;
925 }
926
927 // check all possible child types for this parent
928 foreach (node_get_types('names') as $type=>$name) {
929 $conduit_types = variable_get('relativity_common_child_'.$parent->type.'_'.$type, array());
930 if (in_array($child->type, $conduit_types)) {
931 // make sure no child nodes of type $type exist with $child as a child, as they would require $child here to exist.
932 $result = db_query("SELECT DISTINCT n.nid as nid FROM {node} n INNER JOIN {relativity} r ON n.nid=r.nid WHERE n.type = '%s' AND r.parent_nid = %d", $type, $parent->nid);
933 if (db_num_rows($result) > 0) {
934 return FALSE;
935 }
936 }
937 }
938 return TRUE;
939 }
940
941 /**
942 * See if current user may create a *new* child of type $child_type to a parent of type $parent_type
943 */
944 function relativity_may_attach_new_child_type($parent_type, $child_type) {
945 if (node_access('create', $child_type)) {
946 // make sure relationship is still valid
947 if (in_array($child_type, variable_get('relativity_type_'.$parent_type, array()))) {
948 // make sure relatinship doesn't require a common child
949 if (!variable_get('relativity_common_child_'.$parent_type.'_'.$child_type, FALSE)) {
950 return TRUE;
951 }
952 }
953 else {
954 return TRUE;
955 }
956 }
957 return FALSE;
958 }
959
960 /**
961 * Deletes all rows involving the specified node from the database table
962 */
963 function relativity_delete_relationships($node) {
964 // look to see if this is a required common child that is currently holding relationships together
965 foreach (node_get_types('names') as $ptype=>$pname) {
966 foreach (node_get_types('names') as $chtype=>$chname) {
967 // make sure that the parent/child relationship is still valid
968 if (in_array($chtype, variable_get('relativity_type_'.$ptype, array()))) {
969
970 $conduit_types = variable_get('relativity_common_child_'.$ptype.'_'.$chtype, array());
971 if (in_array($node->type, $conduit_types)) {
972 //drupal_set_message("deleting conduit node of type $node->type. See if there are any dependent $chtype relatives that are children of $ptype nodes");
973 // find all children of type $chtype of this node's parents of type $ptype
974 $dependent_children = relativity_list_grand_relatives($node, 'parent', 'child', array($ptype), array($chtype), FALSE);
975 if (is_array($dependent_children) && count($dependent_children)) {
976 $dependent_children[$node->nid] = $node->nid; // include self in children filter
977
978 // find their parents of type $ptype
979 $result = db_query("SELECT DISTINCT n.nid as nid FROM {node} n INNER JOIN {relativity} r ON n.nid=r.parent_nid WHERE n.type = '%s' AND r.nid IN (".implode(',', array_keys($dependent_children)).")", $ptype);
980 while($obj = db_fetch_object($result)) {
981 // if this potentially dependent parent also has $node as a child, delete it
982 if (db_result(db_query("SELECT count(r.nid) as cnt FROM {relativity} r WHERE r.parent_nid=%d AND r.nid=%d", $obj->nid, $node->nid))) {
983 $dependent_parents[] = $obj->nid;
984 }
985 }
986 if (is_array($dependent_parents) && count($dependent_parents)) {
987 drupal_set_message(t('Removing dependent children (%children) from dependent parents (%parents)', array('%children'=>implode(',', array_keys($dependent_children)), '%parents'=>implode(',', $dependent_parents))));
988 // delete any relationships that require these $dependent_parents to have these $dependent_children
989 db_query("DELETE FROM {relativity} WHERE parent_nid IN (".implode(',', $dependent_parents).") AND nid IN (".implode(',', array_keys($dependent_children)).")");
990 }
991 }
992 }
993 }
994 }
995 }
996 // clear out all relationships directly involving this node
997 db_query('DELETE FROM {relativity} WHERE nid = %d OR parent_nid = %d', $node->nid, $node->nid);
998 }
999
1000 /**
1001 * Implementation of hook_nodeapi().
1002 *
1003 * This hook allows us to act on all major node operations,
1004 * so we can manage our additional data appropriately.
1005 */
1006 function relativity_nodeapi(&$node, $op, $teaser, $page) {
1007 if ($_GET['parent_node']) {
1008 $node->parent_node = $_GET['parent_node'] + 0;
1009 }
1010 elseif($_POST['parent_node']) {
1011 $node->parent_node = $_POST['parent_node'] + 0;
1012 }
1013
1014 switch ($op) {
1015
1016 case 'validate':
1017 if ($node->nid && ($_GET['parent_node'] || $_POST['parent_node'])) {
1018 $parents = explode(',', $node->parent_node);
1019 foreach($parents as $parent_nid) {
1020 $parent = node_load($parent_nid);
1021 if (!$parent || !in_array($node->type, variable_get('relativity_type_'. $parent->type, array()))) {
1022 form_set_error('relativity_type'.$node->type, t('You\'re not allowed to create this type of attachment.'));
1023 }
1024 }
1025 }
1026 break;
1027
1028 case 'insert':
1029 if ($node->nid && $node->parent_node) {
1030 if (is_array($node->parent_node)) {
1031 foreach($node->parent_node as