Index: relativity.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/relativity/relativity.module,v retrieving revision 1.19 diff -u -r1.19 relativity.module --- relativity.module 28 Mar 2005 21:08:11 -0000 1.19 +++ relativity.module 28 Apr 2006 12:46:31 -0000 @@ -1,1586 +1,1786 @@ -$parent_nid)); - $relativity_query = variable_get('relativity_child_query_'.$parent->type.'_'.$type, NULL); - $common_children_reqd = variable_get('relativity_common_child_'.$parent->type.'_'.$type, array()); - if($relativity_query && $query = node_load(array('nid'=>$relativity_query))) { - //drupal_set_message("executing query ".print_r($query,1)); - foreach(relativity_execute_query($query, NULL, $parent_nid) as $chnid => $child) { - if($child->type == $type) { - $links[] = theme("relativity_link",$child->title, "addparent/$child->nid/parent/$parent_nid", $parent_nid, ' '.theme_relativity_trace($child->relativity_trace)); - } - } - } - elseif(count($common_children_reqd)) { - $otherparents = relativity_list_grand_relatives($parent, 'child', 'parent', $common_children_reqd, array($type)); - - foreach($otherparents as $childparent) { - if(node_access("view",$childparent)) { - $links[] = theme("relativity_link",$childparent->title, "addparent/$childparent->nid/parent/$parent_nid", $parent_nid, 'common child required'); - } - } - } - else { - // So, look up all valid nids attached to this parent_node already.. - $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); - $excluded_nids = array(); - // The number of nids returned by this array should be reasonably small. - // It's the number of nodes attached to a given node already. - while($existing_nid = db_fetch_object($result)) { - $excluded_nids[] = $existing_nid->nid; - } - - // if we found some nodes already attached, then use the dreaded NOT IN (..) syntax to get everything but them. - if(count($excluded_nids) > 0) { - $exclusion_list = implode(',', $excluded_nids); - - //drupal_set_message("using dreaded NOT IN ($exclusion_list) syntax."); - // maybe this exclusion could be applied manually in the while loop below - $result = db_query('SELECT DISTINCT n.nid FROM {node} n WHERE n.type = \'%s\' AND n.nid NOT IN ('.$exclusion_list.')', $type); - } - else { - $result = db_query('SELECT DISTINCT n.nid FROM {node} n WHERE n.type = \'%s\'', $type); - } - } - - while($child = db_fetch_object($result)) { - $child_node = node_load(array("nid" => $child->nid)); - if(node_access("view",$child_node)) { - $links[] = theme("relativity_link",$child_node->title, "addparent/$child_node->nid/parent/$parent_nid", $parent_nid); - } - } - - if(count($links)) { - print(theme("page", theme("relativity_bullets", $links))); - } - else { - drupal_set_message(t('There are no available %s items to attach', array('%s'=>node_invoke($type, 'node_name')))); - drupal_goto("node/$parent_nid"); - } -} - -/** - * Lists all grandparents or grandchildren of the given node. - * Additionally, allow the direction of search to change directions by having different - * values for $rel_type1 and $rel_type2. - * @param $node the node to start searching from - * @param $rel_type1 Which direction to look ('parent' or 'child') - * @param $rel_type2 Which direction to look beyond children/parents that were found (grand)('parent' or 'child') - * @param $conduit_types array of connecting node types to restrict the search to - * @param $types array of grandparent/grandchild node types to restrict the results to - * @return array of grandparent/grandchild nodes that were found - */ -function relativity_list_grand_relatives($node, $rel_type1='child', $rel_type2='child', $conduit_types = NULL, $types = NULL, $exclude_circular_paths=TRUE) { - //$nodes = array(); - - // List all children/parents. Restrict list to $conduit_types if specified - $conduit_types_sql = ""; - if(is_array($conduit_types) && count($conduit_types)) { - $conduit_types_sql = " AND n.type IN ('".implode("','",$conduit_types)."') "; - } - - if($rel_type1 == 'parent') { - $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); - } - else { - $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); - } - - while($obj = db_fetch_object($result)) { - $relatives[$obj->nid] = $obj->nid; - } - - if(is_array($relatives) && count($relatives)) { - // list all children/parents of the $relatives found. Restrict list to $conduit_types if specified - - // identify parents and children for exclusion - if($exclude_circular_paths) { - $all_relatives[$node->nid] = $node->nid; // exclude self - - // parents - $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); - while($obj = db_fetch_object($result)) { - $all_relatives[$obj->nid] = $obj->nid; - } - - // children - $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); - while($obj = db_fetch_object($result)) { - $all_relatives[$obj->nid] = $obj->nid; - } - } - $all_relatives = is_array($all_relatives) ? $all_relatives : array(); - - $types_sql = ""; - if(is_array($types) && count($types)) { - $types_sql = " AND n.type IN ('".implode("','",$types)."') "; - } - - if($rel_type2 == 'parent') { - $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"); - } - else { - $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"); - } - - while($obj = db_fetch_object($result)) { - if(!($exclude_circular_paths && isset($all_relatives[$obj->nid]))) { - $grand_relatives[$obj->nid] = node_load(array('nid'=>$obj->nid)); - } - } - } - - return is_array($grand_relatives) ? $grand_relatives : array(); -} - -/** - * Delete the parent/node relationship - */ -function relativity_unparent_node() { - $output = ""; - $parent_nid = arg(2); - $child_nid = arg(3); - if(is_numeric($parent_nid) && is_numeric($child_nid)) { - $parent = node_load(array('nid'=>$parent_nid)); - $child = node_load(array('nid'=>$child_nid)); - if(relativity_may_unchild($parent, $child)) { - $result = db_query('DELETE FROM {relativity} WHERE nid = %d AND parent_nid = %d', $child_nid, $parent_nid); - drupal_set_message(t('Node relationship removed.')); - } - else { - drupal_set_message(t('Node relationship cannot currently be removed.')); - } - } - else { - drupal_set_message(t('Either that node does not exist or you don\'t have proper privileges to update it')); - } - drupal_goto('node/'.$parent_nid); -} - -/** - * Attach a node to it's parent - */ -function relativity_addparent($child_nid="", $parent_nid="") { - if(!$child_nid && !$parent_nid) { - $child_nid = arg(2); - $parent_nid = arg(4); - } - $output = ""; - - db_query('INSERT INTO {relativity} (nid, parent_nid) VALUES (%d, %d)', $child_nid, $parent_nid); - drupal_set_message(t('Node relationship created.')); - drupal_goto('node/'.$parent_nid); -} - -function relativity_menu($may_cache) { - global $_menu;// = menu_get_menu(); - $items = array(); - - if($may_cache && variable_get('relativity_enforce_parent_rules', FALSE)) { - // * not working how I wanted it to - // * This needs node_add in node.module to check menu before displaying links - - // iterate through all node types and take over their node/add handlers - foreach(relativity_node_list() as $type){ - if(relativity_requires_parent($type)) { - //drupal_set_message("setting access for type $type"); - $items[] = array('path' => 'node/add/'. $type, 'title' => t('content type requires parent'), - 'callback' => 'node_page', - 'access' => 0,//user_access('administer content'), - 'type' => MENU_CALLBACK, - 'priority' => 1, - 'weight' => 1 - ); - } - } - } - - if (!$may_cache) { - - if (arg(0) == 'node' && arg(1) == 'add' && in_array(arg(2), relativity_node_list()) && arg(3) == 'parent' && is_numeric(arg(4))) { - // this is my crafty way of creating new nodes as children of parents, and - // not displaying any links to create them otherwise. - $_GET['parent_node'] = arg(4); - $items[] = array('path' => 'node/add/'. arg(2).'/parent/'.arg(4), 'title' => t('create content'), - 'callback' => 'node_page', - 'access' => node_access('create', arg(2)), - 'type' => MENU_CALLBACK, - 'weight' => 1 - ); - } - elseif (arg(0) == 'relativity' && arg(1) == 'listnodes' && in_array(arg(2), relativity_node_list()) && arg(3) == 'parent' && is_numeric(arg(4))) { - - $items[] = array('path' => 'relativity/listnodes/'. arg(2).'/parent/'.arg(4), 'title' => t('list %types to attach', array('%type'=>node_invoke(arg(2), 'node_name'))), - 'callback' => 'relativity_list_possible_children', - 'access' => node_access('update', node_load(array('nid'=>arg(4)))) && user_access("access content"), - 'type' => MENU_CALLBACK - ); - } - elseif (arg(0) == 'relativity' && arg(1) == 'addparent' && is_numeric(arg(2)) && arg(3) == "parent" && is_numeric(arg(4))) { - $items[] = array('path' => 'relativity/addparent/'. arg(2) .'/parent/'. arg(4), 'title' => t('attach node to parent'), - 'callback' => 'relativity_addparent', - 'access' => node_access("view", node_load(array('nid'=>arg(2)))), - 'type' => MENU_CALLBACK - ); - } - elseif (arg(0) == 'relativity' && arg(1) == 'unparent' && is_numeric(arg(2)) && is_numeric(arg(3))) { - $items[] = array('path' => 'relativity/unparent/'.arg(2).'/'.arg(3), 'title' => t('unparent node'), - 'callback' => 'relativity_unparent_node', - 'access' => node_access("update", node_load(array('nid'=>arg(2)))), - 'type' => MENU_CALLBACK - ); - } - } - else { - - $items[] = array('path' => 'node/add/relativity', 'title' => t('node relativity query'), - 'access' => user_access('administer content'), - 'weight' => 1 - ); - - // add "advanced" tab to admin/settings/relativity - $items[] = array('path' => 'admin/settings/relativity/regular', - 'title' => t('regular settings'), - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'weight' => 1, - 'priority' => 1, - 'access' => user_access('administer site configuration') - ); - $items[] = array('path' => 'admin/settings/relativity/display', - 'title' => t('display settings'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 3, - 'priority' => 1, - 'callback' => 'system_site_settings', - 'callback arguments' => array('relativity'), - 'access' => user_access('administer site configuration') - ); - $items[] = array('path' => 'admin/settings/relativity/advanced', - 'title' => t('advanced settings'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 5, - 'priority' => 1, - 'callback' => 'system_site_settings', - 'callback arguments' => array('relativity'), - 'access' => user_access('administer site configuration') - ); - } - return $items; -} - -/** - * Implementation of hook_perm(). - */ -function relativity_perm() { - return array('create node relativity queries', 'edit own node relativity queries'); -} - -// implementation of hook_settings() -function relativity_settings($tab='') { - $output = ""; - $group = ""; - - $label['regular'] = t('Attachment'); - $label['display'] = t('Display'); - $label['advanced'] = t('Attachment'); - - $tab = $tab ? $tab : arg(3); - if(!isset($label[$tab])) { - $tab = 'regular'; - } - $allow_types = relativity_node_list(0,0); - if(!$allow_types || !is_array($allow_types) || !count($allow_types)) { - $group = form_select(t('Specify which Node types relationships should be managed for'), 'relativity_allow_types', variable_get('relativity_allow_types', ""), relativity_node_list(1,t('none'),1), t('What types of nodes should Node Relativity use for configuration? Be sure to include any node type involved (both parent and children types). After saving this, configuration options will appear for all of the node types that you specified.'), 0, TRUE); - return form_group(t('Initialize Relativity Settings'), $group); - } - - if($tab == 'advanced') { - $group .= form_checkbox(t('Enforce Parental Rules'), 'relativity_enforce_parent_rules', 1, variable_get('relativity_enforce_parent_rules', 0), t('If checked, nodes cannot be created without a parent if they require a parent to exist.')); - $group .= form_select(t('Allowable Ancestor Navigation Block Node types'), 'relativity_nav_types', variable_get('relativity_nav_types', ""), relativity_node_list(1), t('What types of nodes are allowed to be used in the navigation block?'), 'size="5"', TRUE); - } - elseif($tab == 'display') { - $group .= form_checkbox(t('Show Ancestor Navigation Above Node Body'), 'relativity_show_nav_above_body', 1, variable_get('relativity_show_nav_above_body', 0), t('If checked, a listing of the node\'s ancestors will be displayed above each node.')); - $group .= form_checkbox(t('Show Ancestor Navigation Below Node Body'), 'relativity_show_nav_below_body', 1, variable_get('relativity_show_nav_below_body', 0), t('If checked, a listing of the node\'s ancestors will be displayed below each node.')); - } - else { - $output .= form_item('', t('Below are the general settings for node relationships for each node type. The %display and %advanced pages use the settings on this page, so be sure to save these settings before proceeding to either of them.', array('%display'=>l($label['display'], 'admin/settings/relativity/display'), '%advanced'=>l($label['advanced'], 'admin/settings/relativity/advanced')))); - $group .= form_select(t('Which Node types Should Relationships be Managed For?'), 'relativity_allow_types', variable_get('relativity_allow_types', ""), relativity_node_list(1,t('none'),1), t('What types of nodes should Node Relativity use for configuration? Be sure to include any node type involved (both parent and children types).'), 'size="5"', TRUE); - $group .= form_textfield(t('Label for Parent Nodes links'), 'relativity_parents_label', variable_get('relativity_parents_label', t('Related Parents')), 60, 250); - $group .= form_textfield(t('Label for Child Nodes links'), 'relativity_children_label', variable_get('relativity_children_label', t('Related Items')), 60, 250); - $group .= form_textfield(t('Label for Related Actions links'), 'relativity_actions_label', variable_get('relativity_actions_label', t('Related Actions')), 60, 250); - $group .= form_textfield(t('Label for Ancestor Navigation links'), 'relativity_ancestors_label', variable_get('relativity_ancestors_label', t('Related Ancestors')), 60, 250); - } - $output .= form_group(t('Global %label Options', array('%label'=>$label[$tab])), $group); - - $group = ""; - if($tab == 'advanced') { - // let admins specify sort order for node types - foreach($allow_types as $type) { - if(!$node_order[$type] = variable_get('relativity_node_order_'.$type, FALSE)) { - // if not set, default them to 1 - $node_order[$type] = 1; - } - } - - // we need a keyed array where numbers are sort order - $sort_options = range(1, count($node_order)); - foreach($sort_options as $opt) { - $options[$opt] = $opt; - } - - // display list of node types sorted by ascending sort order - for($i=1; $i <= count($node_order); $i++) { - foreach($allow_types as $type) { - if($node_order[$type] == $i) { - $group .= form_select(t('Sort Order for %type Nodes', array('%type'=>node_invoke($type, 'node_name'))), 'relativity_node_order_'.$type, variable_get('relativity_node_order_'.$type, ""), $options); - } - } - } - $output .= form_group(t('Node Sorting Options'), $group); - } - - $group = ""; - foreach(relativity_node_list(1,0) as $key=>$type) { - if($tab == 'display') { - $group .= form_checkbox(t('Show parent nodes'), 'relativity_show_parents_'.$key, 1, variable_get('relativity_show_parents_'.$key, 1), t('If checked, a list of all parents will be displayed with each node.')); - $group .= form_checkbox(t('Show children nodes'), 'relativity_show_children_'.$key, 1, variable_get('relativity_show_children_'.$key, 1), t('If checked, a list of all children will be displayed with each node.')); - $group .= form_checkbox(t('Show actions'), 'relativity_show_actions_'.$key, 1, variable_get('relativity_show_actions_'.$key, 1), t('If checked, a list of all possible actions will be displayed with each node.')); - $render_opts = array('title'=>t('title only'),'teaser'=>t('node teaser'),'body'=>t('node body'), 'hide'=>t('hide this child type')); - foreach(relativity_node_list(1,0) as $chkey=>$chtype) { - if(in_array($chkey, variable_get('relativity_type_'.$key, array()))) { - $group .= form_select(t('%type Child Rendering Rule for %ptype Parents', array('%type'=>$chtype, '%ptype'=>$type)), 'relativity_render_'.$key.'_'.$chkey, variable_get('relativity_render_'.$key.'_'.$chkey, 'title'), $render_opts); - } - } - } - elseif($tab == 'advanced') { - foreach(relativity_node_list(1,0) as $chkey=>$chtype) { - if(in_array($chkey, variable_get('relativity_type_'.$key, array()))) { - $group .= form_select(t('%type Child Ordinality for %ptype Parents', array('%type'=>$chtype, '%ptype'=>$type)), 'relativity_child_ord_'.$key.'_'.$chkey, variable_get('relativity_child_ord_'.$key.'_'.$chkey, 'any'), 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'))); - - // "Require Common Child of specified type" feature - // find the intersection of 'relativity_type_'.$key and 'relativity_type_'.$chkey (common allowable child types for parent and child) - $common_child_types = array_intersect(variable_get('relativity_type_'.$key, array()), variable_get('relativity_type_'.$chkey, array())); - if(count($common_child_types)) { - foreach($common_child_types as $cchtype) { - $common_types[$cchtype] = node_invoke($cchtype, 'node_name'); - } - $group .= form_select(t('Require Common Child Node Types for %chtype Children', array('%chtype'=>$chtype)), 'relativity_common_child_'.$key.'_'.$chkey, variable_get('relativity_common_child_'.$key.'_'.$chkey, array()), $common_types, 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.'), 0, TRUE); - } - - // list out possible relativity_queries that could be used to define this relationship type - $possible_queries = NULL; - // 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 ;) - $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 '%\"$chkey\"%'"; - $result = db_query($sql); - while($obj = db_fetch_object($result)) { - $possible_queries[$obj->nid] = $obj->title; - } - if(is_array($possible_queries) && count($possible_queries)) { - $possible_queries[0] = t('none'); - //drupal_set_message($sql." found possible queries: ".print_r($possible_queries,1)); - $group .= form_select(t('Use Relativity Query For Listing %chtype Children', array('%chtype'=>$chtype)), 'relativity_child_query_'.$key.'_'.$chkey, variable_get('relativity_child_query_'.$key.'_'.$chkey, ''), $possible_queries, t('Use the specified relativity query to identify possible children.')); - } - } - } - } - else { - $group .= form_select(t('Parental Ordinality'), 'relativity_parent_ord_'.$key, variable_get('relativity_parent_ord_'.$key, 'any'), array('any'=>t('any'), 'one'=>t('one'), 'none'=>t('none'), 'one or more'=>t('one or more')), t('How many parents can this node type have?')); - $group .= form_select(t('Allowable Child Node types'), 'relativity_type_'.$key, variable_get('relativity_type_'.$key, ""), relativity_node_list(1), t('What types of nodes are allowed to be attached to this type?'), 'size="5"', TRUE); - } - $output .= form_group(t('%label Options for %type (%key) nodes', array('%type'=>$type, '%key' =>$key, '%label'=>$label[$tab])), $group); - $group = ""; - } - return $output; -} - -/** - * Implementation of hook_block(). - * - * Generates navigation block to quickly jump to any of this node's ancestors. - */ -function relativity_block($op = 'list', $delta = 0) { - // The $op parameter determines what piece of information is being requested. - if ($op == 'list') { - // If $op is "list", we just need to return a list of block descriptions. This - // is used to provide a list of possible blocks to the administrator. - $blocks[0]['info'] = t('Navigate through a node\'s ancestors'); - return $blocks; - } - else if ($op == 'view') { - // see if we're viewing a node - if(arg(0) == "node" && is_numeric(arg(1))) { - // see if it's a valid node - $node = node_load(array('nid'=>arg(1))); - if(is_object($node) && node_access('view', $node)) { - $ancestors = relativity_load_ancestors($node); - if(is_array($ancestors) && count($ancestors) > 0) { - $block['subject'] = $node->title; - $block['content'] = theme('relativity_ancestors', $node, $ancestors); - } - } - } - return $block; - } -} - -/** - * Generate an array of ancestors for the given node. - * This will keep looking for more ancestors until a circular path was found, - * if a node has multiple or no parents at all. - * If $load_multi_parent is true, the first multi-parent result found will be placed - * in the output array as an array of nodes. Otherwise, all array elements will be nodes. - */ -function relativity_load_ancestors($node, $load_multi_parent=1) { - $search_max = 5; // make this a settings variable someday - if(is_numeric($node->nid)) { - $ancestors = array(); - $nids = array(); - $search_nid = $node->nid; - $cnt = 0; - $relativity_nav_types = variable_get('relativity_nav_types', array()); - - do { - $keep_going = 0; - $result = db_query("SELECT parent_nid from {relativity} where nid=%d", $search_nid); - $parent_count = db_num_rows($result); - if($parent_count == 1 && $obj = db_fetch_object($result)) { - // make sure we're not pursuing a circular path and that we stop at $search_max parents - if(!in_array($obj->parent_nid, $nids) && $cnt++ < $search_max) { - //drupal_set_message("unshifting node ".$obj->parent_nid." onto the ancestors array"); - $parent_node = node_load(array('nid'=>$obj->parent_nid)); - if(node_access('view', $parent_node) && in_array($parent_node->type, $relativity_nav_types)) { - array_unshift($ancestors, $parent_node); - $nids[] = $obj->parent_nid; - $search_nid = $obj->parent_nid; - $keep_going = 1; - } - } - } - elseif($parent_count > 1 && $load_multi_parent && $cnt++ < $search_max) { - //drupal_set_message("found parent_count == $parent_count, about to add array to ancestors array"); - // perhaps upon encountering multiple parents, it would suffice to print them - // all out at the same level and not follow any of them up, but still allow - // the user to navigate up whatever hierarchy they want to. - $siblings = array(); - while($obj = db_fetch_object($result)) { - // make an array of nodes to display at this level - $parent_node = node_load(array('nid'=>$obj->parent_nid)); - if(!in_array($obj->parent_nid, $nids) && node_access('view', $parent_node) && in_array($parent_node->type, $relativity_nav_types)) { - array_unshift($siblings, $parent_node); - $nids[] = $obj->parent_nid; - $search_nid = $obj->parent_nid; // only used when single result is rendered. - } - } - if(count($siblings) > 0) { - array_unshift($ancestors, $siblings); - } - if(count($siblings) == 1) { - $keep_going = 1; - } - } - } while($keep_going); - return $ancestors; - } -} - -/** - * Executes a relativity_query node using the default target_nid or one specified - * as an optional parameter. - * - * @param $querynode a relativity_query type node containing query info - * @param $target_nid optionally specify a starting node - * @return an array of nodes indexed by nid that were found while searching. - */ -function relativity_execute_query($querynode, $keywords=NULL, $target_nid=NULL) { - if(!$target_nid) { - $target_nid = $querynode->target_nid; - } - $search_results = relativity_list_relatives($target_nid, $querynode->follow_parents, $querynode->follow_children, $querynode->recursion_depth, $querynode->unique_types, $querynode->max_results, $querynode->search_types, $querynode->end_pts, $querynode->avoid_pts, $querynode->options, $querynode->search_algorithm); - if($keywords) { - $search_results = relativity_filter_keywords($search_results, $keywords); - } - //drupal_set_message("running query for ".print_r($querynode,1)." found ".count($search_results)." results"); - return $search_results; -} - -/** - * A wrapper function that identifies the correct search algorithm function and - * invokes it. - * - * @param $nid the node id of the node to search from - * @param $follow_parent_paths if true, traverse parent paths of the graph - * @param $follow_child_paths if true, traverse child paths of the graph - * @param $recurse Specify the maximum neighbor distance to search through (-1 for $max_recursion) - * @param $unique_node_types if true, only follow a node path if no node currently exists in the results of that node's type - * @param $max_results discontinue searching when this many results have been found - * @param $search_types array of node types that are the only types allowed in the results. - * @param $end_types array of node types that are considered an end point, with no searching going beyond that node. - * @param $avoid_types array of node types that will not be included in the results and will not be searched through. - * @param $search_algorithm which search algorithm to use (currently, only 'dfs', or Depth-First Search is supported). - * - * @return an array of nodes indexed by nid that were found while searching. - */ -function relativity_list_relatives($nid, $follow_parent_paths=1, $follow_child_paths=1, $recurse=0, $unique_node_types=0, $max_results=50, $search_types = NULL, $end_types = NULL, $avoid_types = NULL, $options=NULL, $search_algorithm='dfs') { - $search_algorithm = $search_algorithm ? $search_algorithm : 'dfs'; - $search_func = 'relativity_list_relatives_'.$search_algorithm; - if(function_exists($search_func)) { - return $search_func($nid, $follow_parent_paths, $follow_child_paths, $recurse, $unique_node_types, $max_results, $search_types, $end_types, $avoid_types, $options); - } - drupal_set_message(t('Search algorithm \'%algo\' not supported', array('%algo'=>$search_algorithm))); - return array(); -} - -/** - * Navigate DiGraph of nodes upwards (and downwards, too if $follow_child_paths=1) - * and return an array of distinct nids whose values are the node type. - - * logic: - * start with a nid. - * if follow_parent_paths, find all parents of this nid and add them to the nids array - * if the nid is already in the nodes array, skip it - * otherwise, recurse with the parent nodes that were just found - * if follow_child_paths, find all children and add them to the nids array - * if the nid is already in the nodes array, skip it - * otherwise, recurse with the child nodes that were found. - * return the nodes array. - * - * @param $nid the node id of the node to search from - * @param $follow_parent_paths if true, traverse parent paths of the graph - * @param $follow_child_paths if true, traverse child paths of the graph - * @param $recurse Specify the maximum neighbor distance to search through (-1 for $max_recursion) - * @param $unique_node_types if true, only follow a node path if no node currently exists in the results of that node's type - * @param $end_types array of node types that are considered an end point, with no searching going beyond that node. - * @param $avoid_types array of node types that will not be included in the results and will not be searched through. - * @param $init if true, reset static variables to initial state - typically true when called but false upon recursion. - * - * @return an array of nodes indexed by nid that were found while searching. - */ -function relativity_list_relatives_dfs($nid, $follow_parent_paths=1, $follow_child_paths=1, $recurse=0, $unique_node_types=0, $max_results=50, $search_types = NULL, $end_types = NULL, $avoid_types = NULL, $options=NULL, $init=1) { - global $user; - static $nodes = array(); - static $node_types = array(); - static $visited = array(); - static $trace = array(); - static $depth = 0; - $search_max = 500; //sanity check - $max_recursion = 500; //sanity check - $search_sql = array(); - - // NOTE: this may be dangerous here, as we're importing items into the namespace - // of this function. Current known items: do_trace, my_nodes_only, display_format - if(is_array($options)) { - extract($options); - } - - if($init) { - $nodes = array(); - $node_types = array(); - $trace = array(); - $depth = 0; - if($recurse == -1) { - $recurse = $max_recursion; - } - if(!$max_results) { - $max_results = 50; - } - $visited = array(); - //drupal_set_message("init: list_relatives_dfs($nid, follow_parent_paths: $follow_parent_paths, follow_child_paths: $follow_child_paths, recurse: $recurse, unique_node_types: $unique_node_types, max_results: $max_results, search_types: $search_types(".count($search_types)."), end_types: $end_types(".count($end_types)."), avoid_types: $avoid_types(".count($avoid_types)."), options: ".print_r($options,1).", init: $init, depth: $depth"); - - } - - // don't traverse the same node multiple times. - if(in_array($nid, $visited)) { - //drupal_set_message("already visited this node ($nid)"); - return $nodes; - } - - //drupal_set_message("list_relatives_dfs($nid, $follow_parent_paths, $follow_child_paths, $recurse, $unique_node_types, $max_results, $search_types(".count($search_types)."), $end_types(".count($end_types)."), $avoid_types(".count($avoid_types)."), $init), options: ".print_r($options,1).", depth: $depth"); - - if($follow_parent_paths) { - $search_sql['parent'] = "SELECT parent_nid as the_nid from {relativity} where nid=%d"; - } - - if($follow_child_paths) { - $search_sql['child'] = "SELECT nid as the_nid from {relativity} where parent_nid=%d"; - } - - if(!is_array($search_types) || !count($search_types)) { - $search_types = relativity_node_list(0,0); - } - - if(!is_array($end_types)) { - $end_types = array(); - } - - if(!is_array($avoid_types)) { - $avoid_types = array(); - } - - if(count($search_types)) { - $restrict_search_types = TRUE; - } - else { - $restrict_search_types = FALSE; - } - - if(is_numeric($nid)) { - $visited[] = $nid; // log the fact that we've been here - $cnt = 0; - - // - // there's an odd case where the starting node needs to be included in the results - // This code only gets executed during the initial pass - if($init) { - $relative = node_load(array('nid'=>$nid)); - $relative->relativity_depth = $depth; - - //drupal_set_message("init loop, considering starting node: $nid..." . print_r($relative, 1)); - if($relative->type && (!$my_nodes_only || $user->uid == $relative->uid) && !in_array($relative->type, $avoid_types) && node_access('view', $relative) && !($unique_node_types && isset($node_types[$relative->type]))) { - $relative->recursion_depth = $depth; - - if($restrict_search_types && in_array($relative->type, $search_types)) { - //drupal_set_message("starting node matched all the search rules, so add it to the results"); - $nodes[$obj->the_nid] = $relative; - $node_types[$relative->type] = 1; - } - } - else { - //drupal_set_message("starting node did NOT match the search filter, so don't add it to the results - currently size ".count($nodes)); - } - } - - $depth++; - - //drupal_set_message("following nid $nid..."); - - foreach($search_sql as $direction=>$sql) { - $result = db_query($sql, $nid); - while($obj = db_fetch_object($result)) { - - // we've reached our maximum result set size, bail out now - if(count($nodes) >= $max_results) { - return $nodes; - } - - // quick efficiency break here: - // if unique_node_types are required, and $nodes is the same size as $search_types, we're done - if($unique_node_types && count($nodes) >= count($search_types)) { - return $nodes; - } - - // make sure we're not pursuing a circular path and that we stop at $search_max parents - if($obj->the_nid && !isset($nodes[$obj->the_nid]) && $cnt++ < $search_max) { - $relative = node_load(array('nid'=>$obj->the_nid)); - $relative->relativity_depth = $depth; - - if($relative->type && (!$my_nodes_only || $user->uid == $relative->uid) && !in_array($relative->type, $avoid_types) && node_access('view', $relative) && !($unique_node_types && isset($node_types[$relative->type]))) { - $relative->recursion_depth = $depth; - //$nodes[$obj->the_nid] = $relative->type.": ".$relative->title; // for debugging - if($do_trace) { - array_push($trace, $nid); - //drupal_set_message("list_relatives_dfs: depth: $depth, trace: ".print_r($trace, 1)); - } - - if($restrict_search_types && in_array($relative->type, $search_types)) { - if($do_trace) { - $relative->relativity_trace = $trace; - } - $nodes[$obj->the_nid] = $relative; - $node_types[$relative->type] = 1; - } - else { - //drupal_set_message("skipping $relative->nid (type: $relative->type) as it was not in search_types: " . print_r($search_types,1)); - } - - if($depth < $recurse && !in_array($relative->type, $end_types)) { - //drupal_set_message("recursing for parent ".$obj->the_nid); - relativity_list_relatives_dfs($obj->the_nid, $follow_parent_paths, $follow_child_paths, $recurse, $unique_node_types, $max_results, $search_types, $end_types, $avoid_types, $options, 0); - } - if($do_trace) { - array_pop($trace); - } - } - } - } - } - $depth--; // step back up the search path - } - return $nodes; -} - -function relativity_list_children($nid, $recurse=0, $unique_node_types=0, $max_results=50, $search_types = NULL, $end_types = NULL, $avoid_types = NULL, $do_trace=NULL) { - return relativity_list_relatives($nid, 0, 1, $recurse, $unique_node_types, $max_results, $search_types, $end_types, $avoid_types, $do_trace); -} - -function relativity_list_parents($nid, $recurse=0, $unique_node_types=0, $max_results=50, $search_types = NULL, $end_types = NULL, $avoid_types = NULL, $do_trace=NULL) { - return relativity_list_relatives($nid, 1, 0, $recurse, $unique_node_types, $max_results, $search_types, $end_types, $avoid_types, $do_trace); -} - -/** - * Determines if the specified parent node may add a child of type $type. - * @return true if possible, false otherwise. - */ -function relativity_may_add_child($parent, $type) { - $common_children_reqd = variable_get('relativity_common_child_'.$parent->type.'_'.$type, array()); - if(count($common_children_reqd)) { - // 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 - - // lookup all existing children and see if any of them are on the common_children_reqd list - $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); - while($child = db_fetch_object($result)) { - $children[] = $child->nid; - } - // no common children defined for the parent, reject the request - if(!$children || !is_array($children) || !count($children)) { - return false; - } - - // now, see if any of the children that were found have a parent of type $type - $children = implode(',', $children); - $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); - $count = db_result($result); - if(!$count) { - return $false; - } - } - - $ord = variable_get('relativity_child_ord_'.$parent->type.'_'.$type, 'any'); - if($ord == 'any') { - return true; - } - else { - // look up how many children this parent currently has of the specified type - $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)); - if($res->cnt >= $ord) { - return false; - } - else { - return true; - } - } -} - -/** - * Takes an array of nodes (keyed with nid) and a keyword string and checks to - * see if any of them match search words in the search_index table - */ -function relativity_filter_keywords($search_results, $keyword_string) { - //$keywords = preg_split("/[^A-Za-z0-9_\-\+\$\@\#\']+/", $keyword_string); // split strings on any non alpha-num char - $keywords = preg_split("/[\s,]+/", $keyword_string); // split strings on any whitespace or comma - - // no valid keywords were specified, so just disregard keyword search filtering - if(!count($keywords)) { - return $search_results; - } - - if(is_array($search_results) && count($search_results)) { - $placeholders = array(); - foreach($keywords as $k=>$val) { - // Remove punctuation/special characters (same rule as update_index()). - $keywords[$k] = preg_replace("'(!|%|,|:|;|\(|\)|\&|\"|\'|\.|-|\/|\?|\\\)'", '', $val); - $kplaceholders[] = "'%s'"; - } - unset($search_results['']); // for some reason, there's an empty key in here that's messing things up - - $nids = implode(',', array_keys($search_results)); - $results = db_query("SELECT lno AS nid, SUM(count) AS rating FROM {search_index} WHERE type='node' AND lno IN ($nids) AND word IN (".implode(',', $kplaceholders).") GROUP BY lno ORDER BY rating DESC;", $keywords); - while($obj = db_fetch_object($results)) { - $node = $search_results[$obj->nid]; - $node->relativity_keyword_rating = $obj->rating; - $filtered_results[$node->nid] = $node; - } - } - if($filtered_results) { - return $filtered_results; - } - return array(); -} - -/** - * implementation of hook_access - * - * New usage: - * Without the flexinode hack pointing back here for access privileges, I've - * removed the old code and now manage access to relativity_query node types. - * - * Old usage: - * Iterate through all node types and disable the creation of nodes that require - * a parent unless a $_GET['parent_node'] param is present. - * This doesn't work using current system. This is being called explicitly by flexinode_access. - * - */ -function relativity_access($op, $node) { - global $user; - switch($op) { - case 'create': - if($node == 'relativity' && user_access('create node relativity queries')) { - return TRUE; - } - elseif(relativity_requires_parent($node) && !isset($_GET['parent_node'])) { - //drupal_set_message("relativity_access denying access here for node type $node, parent=$parent_nid"); - return FALSE; - } - - break; - - case 'update': - case 'delete': - if($node->uid == $user->uid) { - return user_access('edit own node relativity queries'); - } - break; - - } -} - -function relativity_access_denied() { - print theme('page', message_access(), t('Access denied')); -} - -/** - * Convenience function for determining if a node requires a parent to exist. - */ -function relativity_requires_parent($node) { - if(is_object($node)) { - $type = $node->type; - } - else { - $type = $node; - } - if(variable_get("relativity_parent_ord_$type", "") == 'one' || - variable_get("relativity_parent_ord_$type", "") == 'one or more') { - return true; - } - - return false; -} - -/** - * Convenience function for determining if a node may have more than one parent. - */ -function relativity_multi_parent($node) { - if(is_object($node)) { - $type = $node->type; - } - else { - $type = $node; - } - if(variable_get("relativity_parent_ord_$type", "") == 'any' || - variable_get("relativity_parent_ord_$type", "") == 'one or more') { - return true; - } - return false; -} - -/** - * If this node is required to connect a series of nodes together, return FALSE - */ -function relativity_may_unchild($parent, $child) { - - if(!node_access("update", $parent)) { - return FALSE; - } - - // check all possible child types for this parent - foreach(node_list() as $type) { - $conduit_types = variable_get('relativity_common_child_'.$parent->type.'_'.$type, array()); - if(in_array($child->type, $conduit_types)) { - // make sure no child nodes of type $type exist with $child as a child, as they would require $child here to exist. - $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); - if(db_num_rows($result) > 0) { - return FALSE; - } - } - } - return TRUE; -} - -/** - * See if current user may create a *new* child of type $child_type to a parent of type $parent_type - */ -function relativity_may_attach_new_child_type($parent_type, $child_type) { - if(node_access('create', $child_type)) { - // make sure relationship is still valid - if(in_array($child_type, variable_get('relativity_type_'.$parent_type, array()))) { - // make sure relatinship doesn't require a common child - if(!variable_get('relativity_common_child_'.$parent_type.'_'.$child_type, FALSE)) { - return TRUE; - } - } - else { - return TRUE; - } - } - return FALSE; -} - -/** - * Deletes all relationships involved with the specified node - */ -function relativity_delete_relationships($node) { - // look to see if this is a required common child that is currently holding relationships together - foreach(node_list() as $ptype) { - foreach(node_list() as $chtype) { - // make sure that the parent/child relationship is still valid - if(in_array($chtype, variable_get('relativity_type_'.$ptype, array()))) { - - $conduit_types = variable_get('relativity_common_child_'.$ptype.'_'.$chtype, array()); - if(in_array($node->type, $conduit_types)) { - //drupal_set_message("deleting conduit node of type $node->type. See if there are any dependent $chtype relatives that are children of $ptype nodes"); - // find all children of type $chtype of this node's parents of type $ptype - $dependent_children = relativity_list_grand_relatives($node, 'parent', 'child', array($ptype), array($chtype), FALSE); - if(is_array($dependent_children) && count($dependent_children)) { - $dependent_children[$node->nid] = $node->nid; // include self in children filter - - // find their parents of type $ptype - $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); - while($obj = db_fetch_object($result)) { - // if this potentially dependent parent also has $node as a child, delete it - 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))) { - $dependent_parents[] = $obj->nid; - } - } - if(is_array($dependent_parents) && count($dependent_parents)) { - drupal_set_message(t('Removing dependent children (%children) from dependent parents (%parents)', array('%children'=>implode(',', array_keys($dependent_children)), '%parents'=>implode(',', $dependent_parents)))); - // delete any relationships that require these $dependent_parents to have these $dependent_children - db_query("DELETE FROM {relativity} WHERE parent_nid IN (".implode(',', $dependent_parents).") AND nid IN (".implode(',', array_keys($dependent_children)).")"); - } - } - } - } - } - } - // clear out all relationships directly involving this node - db_query('DELETE FROM {relativity} WHERE nid = %d OR parent_nid = %d', $node->nid, $node->nid); -} - -/** - * Implementation of hook_nodeapi(). - * - * We will implement several node API operations here. This hook allows us to - * act on all major node operations, so we can manage our additional data - * appropriately. - */ -function relativity_nodeapi(&$node, $op, $teaser, $page) { - if($_GET['parent_node']) { - $node->parent_node = $_GET['parent_node'] + 0; - } - elseif($_POST['parent_node']) { - $node->parent_node = $_POST['parent_node'] + 0; - } - - switch ($op) { - - case 'form pre': - if($node->parent_node) { - if(is_array($node->parent_node)) { - $output = form_hidden("parent_node", implode(',',$node->parent_node)); - } - else { - $output = form_hidden("parent_node", $node->parent_node); - } - } - if ($types = variable_get('relativity_type_'. $node->type, FALSE)) { - $output .= theme("relativity_links", $types, $node, 'edit'); - } - return $output; - - case 'validate': - if ($node->nid && ($_GET['parent_node'] || $_POST['parent_node'])) { - $parents = explode(',', $node->parent_node); - foreach($parents as $parent_nid) { - $parent = node_load(array('nid' => $parent_nid)); - if(!$parent || !in_array($parent->type, variable_get('relativity_type_'. $node->type, array()))) { - form_set_error('relativity_type'.$node->type, t('You\'re not allowed to create this type of attachment.')); - } - } - } - break; - - case 'insert': - if ($node->nid && $node->parent_node) { - if(is_array($node->parent_node)) { - foreach($node->parent_node as $parent_node) { - db_query('INSERT INTO {relativity} (nid, parent_nid) VALUES (%d, %d)', $node->nid, $parent_node); - } - } - else { - db_query('INSERT INTO {relativity} (nid, parent_nid) VALUES (%d, %d)', $node->nid, $node->parent_node); - } - } - break; - - // remove all relationships to or from this node - case 'delete': - if ($node->parent_node) { - relativity_delete_relationships($node); - } - break; - - // if there is a parent to this node, create a node property named "parent_node" - // which contains the nid of its parent. If there are more than one parent, - // this will be an array of such nids. - case 'load': - $result = db_query('SELECT parent_nid FROM {relativity} WHERE nid = %d', $node->nid); - if(db_num_rows($result) > 1) { - $parent_node = array(); - while($object = db_fetch_object($result)) { - $parent_node[] = $object->parent_nid; - } - } - elseif(db_num_rows($result) == 1) { - $object = db_fetch_object($result); - $parent_node = $object->parent_nid; - } - if($parent_node) { - return array('parent_node' => $parent_node); - } - else { - return array(); - } - break; - - case 'view': - $output = ""; - if(variable_get('relativity_show_parents_'.$node->type, TRUE)) { - $output .= theme('relativity_show_parents', $node); - } - - if(variable_get('relativity_show_children_'.$node->type, TRUE)) { - $output .= theme('relativity_show_children', $node); - } - - if ($types = variable_get('relativity_type_'. $node->type, FALSE)) { - $output .= theme("relativity_links", $types, $node, 'view'); - } - $node->body .= $output; - if(variable_get('relativity_show_nav_above_body', FALSE)) { - $nav = relativity_block('view'); - $node->body = $nav['content'] ? form_group(variable_get('relativity_ancestors_label', t('Related Ancestors')), $nav['content']) . $node->body : $node->body; - } - if(variable_get('relativity_show_nav_below_body', FALSE)) { - $nav = relativity_block('view'); - $node->body = $node->body . $nav['content'] ? form_group(variable_get('relativity_ancestors_label', t('Related Ancestors')), $nav['content']) : ''; - } - break; - } -} - -// This section handles Node Relativity queries -// queries have the following fields: -// nid, title, target_nid, follow_parents, follow_children, recursion_depth, end_pts, avoid_pts - -/** - * implementation of hook_node_name() - */ -function relativity_node_name($node) { - return t('node relativity query'); -} - -/** - * Implementation of hook_form(). - */ -function relativity_form(&$node, &$error) { - $output = ''; - $max_search=100; - - if($_GET['edit']['target_nid'] && is_numeric($_GET['edit']['target_nid'])) { - $output .= form_hidden('target_nid', $_GET['edit']['target_nid']); - } - else { - $output .= form_textfield(t('Specify the node id (nid) of the starting node'), 'target_nid', $node->target_nid, 10, 25); - } - // Now we define the form elements specific to our node type. - $output .= form_checkbox(t('Follow Parent Paths'), 'follow_parents', 1, $node->follow_parents, t('If checked, the search engine will follow parent-oriented paths while searching for results.')); - $output .= form_checkbox(t('Follow Child Paths'), 'follow_children', 1, $node->follow_children, t('If checked, the search engine will follow child-oriented paths while searching for results.')); - $output .= form_checkbox(t('Require Unique Types'), 'unique_types', 1, $node->unique_types, t('If checked, the search engine will return only the first instance of each node type found.')); - - $options = array(); - // $options[-1] = t('use max'); // for some reason, this is not working (and I don't care enough to fix it ;) - foreach(range(1,$max_search) as $i) { - $options[$i] = $i; - } - $output .= form_select(t('Max Search Results'), 'max_results', isset($node->max_results)?$node->max_results:'', $options, t('The maximum number of results to return.')); - $output .= form_select(t('Search Distance'), 'recursion_depth', isset($node->recursion_depth)?$node->recursion_depth:10, $options, t('How many levels away from the starting node should be searched (recursion depth)?')); - $output .= form_select(t('Search Algorithm'), 'search_algorithm', $node->search_algorithm, array('dfs' => t('Depth-First Search')), t('Specify the search algorithm to use for searching the network of nodes (currently, only DFS is supported).')); - $output .= form_select(t('Search Node types'), 'search_types', $node->search_types, relativity_node_list(1,0), t('Specify which node types to search for. Only include nodes of this type in the results (leave blank for all).'), 0, TRUE); - $output .= form_select(t('End Point Node types'), 'end_pts', $node->end_pts, relativity_node_list(1,0), t('Specify which node types to stop searching beyond, but include in the results.'), 0, TRUE); - $output .= form_select(t('Avoid Node types'), 'avoid_pts', $node->avoid_pts, relativity_node_list(1,0), t('Specify which node types to avoid completely. These will not be included in the results.'), 0, TRUE); - $output .= form_select(t('Display Format'), 'display_format', $node->options['display_format'] ? $node->options['display_format'] : 'title', array('title'=>t('title only'),'teaser'=>t('node teaser'),'body'=>t('node body')), t('Specify the way in which the results should be formatted.')); - $output .= form_checkbox(t('Perform Search Trace'), 'do_trace', 1, $node->options['do_trace'], t('If checked, the search engine will log each node that was found leading up to each result.')); - $output .= form_checkbox(t('Only Search My Nodes'), 'my_nodes_only', 1, $node->options['my_nodes_only'], t('If checked, the search engine will only consider noded created by the current user when it is searching for nodes.')); - $output .= form_checkbox(t('Display Search Node Form Field'), 'show_search_node_field', 1, $node->options['show_search_node_field'], t('If checked, the search page will display a form field allowing viewers to specify an alternate starting node.')); - $output .= form_checkbox(t('Display Search Keyword Form Field'), 'show_keyword_field', 1, $node->options['show_keyword_field'], t('If checked, the search page will display a form field allowing viewers to specify a keyword to filter the results on.')); - - return $output; -} - -/** - * Implementation of hook_validate(). - * - * This hook lets - * us ensure that the user entered an appropriate value before we try - * inserting anything into the database. - */ -function relativity_validate(&$node) { - if ($_POST['edit'] && !$node->follow_parents && !$node->follow_children) { - form_set_error('follow_parents', t('Either "Follow Parent Paths" or "Follow Child Paths" must be selected.')); - } - if(!$node->search_algorithm) { - $node->search_algorithm = 'dfs'; - } - if(!$node->max_results) { - $node->max_results = 50; - } - // squiggle around the additional db fields - if($node->display_format) { - $node->options['display_format'] = $node->display_format; - } - if($node->do_trace) { - $node->options['do_trace'] = $node->do_trace; - } - if($node->my_nodes_only) { - $node->options['my_nodes_only'] = $node->my_nodes_only; - } - if($node->show_search_node_field) { - $node->options['show_search_node_field'] = $node->show_search_node_field; - } - if($node->show_keyword_field) { - $node->options['show_keyword_field'] = $node->show_keyword_field; - } -} - -/** - * Implementation of hook_insert(). - */ -function relativity_insert($node) { - db_query("INSERT INTO {relativity_query} (nid, target_nid, follow_parents, follow_children, recursion_depth, unique_types, max_results, search_algorithm, search_types, end_pts, avoid_pts, options) VALUES (%d, %d, %d, %d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')", $node->nid, $node->target_nid, $node->follow_parents, $node->follow_children, $node->recursion_depth, $node->unique_types, $node->max_results, $node->search_algorithm, serialize($node->search_types), serialize($node->end_pts), serialize($node->avoid_pts), serialize($node->options)); -} - -/** - * Implementation of hook_update(). - */ -function relativity_update($node) { - db_query("UPDATE {relativity_query} SET target_nid = %d, follow_parents = %d, follow_children = %d, recursion_depth = %d, unique_types = %d, max_results = %d, search_algorithm = '%s', search_types = '%s', end_pts = '%s', avoid_pts = '%s', options = '%s' WHERE nid = %d", $node->target_nid, $node->follow_parents, $node->follow_children, $node->recursion_depth, $node->unique_types, $node->max_results, $node->search_algorithm, serialize($node->search_types), serialize($node->end_pts), serialize($node->avoid_pts), serialize($node->options), $node->nid); -} - -/** - * Implementation of hook_delete(). - * - * When a node is deleted, we need to clean up related tables. - */ -function relativity_delete($node) { - db_query('DELETE FROM {relativity_query} WHERE nid = %d', $node->nid); -} - -/** - * Implementation of hook_load(). - * - * Now that we've defined how to manage the node data in the database, we - * need to tell Drupal how to get the node back out. This hook is called - * every time a node is loaded, and allows us to do some loading of our own. - */ -function relativity_load($node) { - $additions = db_fetch_object(db_query('SELECT target_nid, follow_parents, follow_children, recursion_depth, unique_types, max_results, search_algorithm, search_types, end_pts, avoid_pts, options FROM {relativity_query} WHERE nid = %d', $node->nid)); - $additions->search_types = unserialize($additions->search_types); - $additions->end_pts = unserialize($additions->end_pts); - $additions->avoid_pts = unserialize($additions->avoid_pts); - $additions->options = unserialize($additions->options); - return $additions; -} - -/** - * Implementation of hook_view(). - * - * The query node is executed and results are themed. - */ -function relativity_view(&$node, $teaser = FALSE, $page = FALSE) { - $node->teaser = theme('relativity_query_form',$node); - $node->body = $node->teaser; - - if($_GET['edit']['target_nid']) { - $node->body .= theme('relativity_results', relativity_execute_query($node, $_GET['edit']['keywords'], $_GET['edit']['target_nid']), t('Search Results'), $node->options); - } - elseif($node->target_nid) { - $node->body .= theme('relativity_results', relativity_execute_query($node, $_GET['edit']['keywords']), t('Search Results'), $node->options); - } - -} - -/** - * Sorts an array of node types based on admin specification - * @param an array of node type names to sort or empty to use node_list() - * @return an array of sorted node type names, unindexed - */ -function relativity_sort_types($types=NULL) { - static $sorted_node_list; - - if(!is_array($sorted_node_list)) { - foreach(range(1, count(node_list())) as $idx) { - foreach(node_list() as $type) { - if(variable_get('relativity_node_order_'.$type, 1) == $idx) { - $sorted_node_list[] = $type; - } - } - } - } - - if(!$types) { - // cut it short and return the presorted static node list - return $sorted_node_list; - } - - // discard garbage - if(!is_array($types)) { - return array(); - } - - // nothing to sort if there's less than 2 elements - if(count($types) < 2) { - return $types; - } - - foreach($sorted_node_list as $type) { - if(in_array($type, $types)) { - $sorted_types[] = $type; - } - } - - return is_array($sorted_types) ? $sorted_types : array(); -} - -/** - * Convenience function to load just a node's type and title for display purposes - */ -function relativity_load_nodeinfo($nid) { - static $node_data = array(); - //drupal_set_message("loading node info for nid $nid"); - if(!$node_data[$nid]) { - $obj = db_fetch_object(db_query('SELECT n.type, n.title FROM {node} n WHERE nid=%d', $nid)); - $node_data[$nid] = array('type'=>$obj->type, 'title'=>$obj->title); - } - return $node_data[$nid]; -} - -function theme_relativity_show_parents($node) { - // load all nodes associated with this one as the parent nid and show links to all of them. - $result = db_query('SELECT parent_nid as the_nid FROM {relativity} WHERE nid = %d', $node->nid); - while($relative = db_fetch_object($result)) { - $parent_nodes[] = node_load(array("nid" => $relative->the_nid)); - } - - // sort output by node type - if(is_array($parent_nodes)) { - foreach(relativity_sort_types() as $type) { - foreach($parent_nodes as $parent_node) { - if($parent_node->type == $type) { - $links .= "'; - } - } - } - } - if($links) { - $output .= form_group(variable_get('relativity_parents_label', t('Related Ancestors')), $links); - } - - if($output) { - return $output; - } - -} - -function theme_relativity_show_children($node) { - // load all nodes associated with this one as the parent nid and show links to all of them. - $result = db_query('SELECT nid FROM {relativity} WHERE parent_nid = %d', $node->nid); - while($child = db_fetch_object($result)) { - $child_nodes[] = node_load(array("nid" => $child->nid)); - } - // sort output by node type - if(is_array($child_nodes)) { - foreach(relativity_sort_types() as $type) { - foreach($child_nodes as $child_node) { - if($child_node->type == $type) { - switch(variable_get('relativity_render_'.$node->type.'_'.$child_node->type, 'title')) { - case 'title': - $links .= "'; - break; - case 'teaser': - $links .= "'; - break; - case 'body': - $links .= "'; - break; - } - } - } - } - } - if($links) { - $output .= form_group(variable_get('relativity_children_label', t('Related Items')), $links); - } - - if($output) { - return $output; - } -} - -/** - * themes the output for the query search form using options within the node - */ -function theme_relativity_query_form($node) { - if(!$node) { - return ""; - } - if(is_array($node->options)) { - extract($node->options); - } - - if($show_search_node_field) { - $frm = form_textfield(t('Specify the node id (nid) of the starting node'), 'target_nid', $_GET['edit']['target_nid']? $_GET['edit']['target_nid'] : $node->target_nid, 10, 25); - } - if($show_keyword_field) { - $frm .= form_textfield(t('Search Words (optional)'), 'keywords', $_GET['edit']['keywords'], 20, 255); - } - if($show_search_node_field || $show_keyword_field) { - $frm .= form_submit("Search"); - return form($frm, 'GET', "node/$node->nid"); - } - return ""; -} - -function theme_relativity_trace($trace="") { - $output = ""; - if(!$trace || !is_array($trace)) { - return ""; - } - foreach($trace as $nid) { - $info = relativity_load_nodeinfo($nid); // returns nid, type and title for node - $links[] = l($info['title'], 'node/'.$nid); - } - return "[".implode(" >> ", $links)."]"; -} - -function theme_relativity_results($nodes, $title, $options) { - if(is_array($options)) { - extract($options); - } - $display_format = $display_format ? $display_format : 'title'; - - //drupal_set_message("nodes: ".print_r(array_keys($nodes))); - // sort output by node type - foreach(relativity_sort_types() as $type) { - foreach($nodes as $nid=>$node) { - if($node->type == $type) { - switch($display_format) { - case 'title': - $number = module_invoke('comment', 'num_all', $node->nid); - $items[] = "
".l(node_invoke($node->type, 'node_name').": ".$node->title, 'node/'. $node->nid, $number ? array('title' => format_plural($number, '1 comment', '%count comments')) : '') .' '. t('(Distance: %depth)', array('%depth'=>$node->recursion_depth)).theme_relativity_trace($node->relativity_trace).'
'; - break; - case 'teaser': - $items[] = "
".form_group(node_invoke($node->type, 'node_name'). t(' (Distance: %depth)', array('%depth'=>$node->recursion_depth)).": ", theme_relativity_trace($node->relativity_trace).node_view($node, TRUE)).'
'; - break; - case 'body': - $items[] = "
".form_group(node_invoke($node->type, 'node_name'). t(' (Distance: %depth)', array('%depth'=>$node->recursion_depth)).": ", theme_relativity_trace($node->relativity_trace).node_view($node, FALSE)).'
'; - break; - } - } - } - } - - drupal_set_title($title); - return $items? implode("\n", $items) : ""; -} - -/** - * This function themes the output of the $types array. It should - * print a list of links to attach a node. - */ -function theme_relativity_links($types, $parent, $op='view') { - $parent_nid = $parent->nid; - - if(!is_array($types) || count($types) == 0 || !$types[0] || !$parent_nid) { - return; - } - - $types = relativity_sort_types($types); - - foreach($types as $type) { - if(relativity_may_add_child($parent, $type)) { - $type_name = node_invoke($type, 'node_name'); - if(!$type_name) { - $type_name = $type; - } - - $may_create = relativity_may_attach_new_child_type($parent->type, $type); - - // only offer to let user create new child if user has 'create' access and common child relationship isn't required - if($may_create && node_access('update', $parent)) { - $output .= l(t('create new %type', array('%type'=>$type_name)), "node/add/$type/parent/$parent_nid") . "
\n"; - } - - // only show link to attach existing node when the potential child doesn't require a parent. - if((!relativity_requires_parent($type) || relativity_multi_parent($type)) && node_access('update', $parent)) { - // Use 'create' privilege to change up the link text - if($may_create) { - $ltitle = t('attach existing %type', array('%type'=>$type_name)); - } - else { - $ltitle = t('attach %type', array('%type'=>$type_name)); - } - $output .= l($ltitle, "relativity/listnodes/$type/parent/$parent_nid") . "
\n"; - } - } - } - - if($op == 'edit') { - // show attachment removal links - $result = db_query('SELECT nid FROM {relativity} WHERE parent_nid = %d', $parent_nid); - $output .= '
'; - while($child = db_fetch_object($result)) { - $child_node = node_load(array("nid" => $child->nid)); - // make sure that no dependencies exist that would require this node to be attached - if(relativity_may_unchild($parent, $child_node)) { - $removables[] = $child_node; - } - } - if(is_array($removables)) { - // sort output by node type - foreach(relativity_sort_types() as $type) { - foreach($removables as $child_node) { - if($child_node->type == $type) { - $output .= theme("relativity_link", '['.t('Remove ').node_invoke($child_node->type, 'node_name').": ".$child_node->title.']', 'unparent/'.$parent_nid.'/'.$child_node->nid); - } - } - } - } - } - - if($output) { - return form_group(variable_get('relativity_actions_label', t('Related Actions')), $output); - } -} - -/** - * This function themes the output of the $types array. It should - * print a list of links to attach a node. - */ -function theme_relativity_link($title, $target, $parent_nid=0, $extra='') { - $output = ''; - return $output; -} - -function theme_relativity_ancestors($node, $ancestors) { - $content = ""; - $indent = 0; - - foreach($ancestors as $ancestor) { - if(is_array($ancestor)) { - foreach($ancestor as $sibling) { - $content .= theme("relativity_ancestor", $sibling, $indent); - } - $indent++; - } - else { - $content .= theme("relativity_ancestor", $ancestor, $indent++); - } - } - return $content; -} - -function theme_relativity_ancestor($ancestor, $indent=0) { - if(!$ancestor->type||!$ancestor->title||!$ancestor->nid) { - return ""; - } - $content .= "
"; - $content .= l("".node_invoke($ancestor->type, 'node_name').":
".$ancestor->title, 'node/'.$ancestor->nid); - $content .= "
"; - return $content; -} - -function theme_relativity_bullets($items) { - return ""; -} -?> +$type) { + if ($type != 'default') { + $node_list[$type] = node_get_name($type); + } + } + } + if ($use_blank) { + $node_list['default'] = t($use_blank); + } + return $node_list; +} +/** + * Lists nodes that could be attached to a given parent type. + * + * TODO: + * Alter this code so that node_access("view",$child) logic is impelemented in the SQL statement. + * Use pager_query to process the results. Maybe even have a sortable table + * with various filters for restricting what is seen. Someday, maybe :) + */ +function relativity_list_possible_children($type="", $parent_nid="") { + if (!$type && !$parent_nid) { + $type = arg(2); + $parent_nid = is_numeric(arg(4)) ? arg(4) : $_GET['parent_node'] + 0; + } + + $links = array(); + // As I age, my SQL skills dumb down, it appears. I can't seem to craft a single + // query to get all nodes in the node table of a certain type that don't already + // exist in the relativity table for the specified parent. I could do it using a subselect, + // but that would keep it from running on half the mysql servers out there. + // Must be pre-Alzheimer's or something. + $parent = node_load($parent_nid); + $relativity_query = variable_get('relativity_child_query_'.$parent->type.'_'.$type, NULL); + $common_children_reqd = variable_get('relativity_common_child_'.$parent->type.'_'.$type, array()); + if ($relativity_query && $query = node_load($relativity_query)) { + //drupal_set_message("executing query ".print_r($query,1)); + foreach(relativity_execute_query($query, NULL, $parent_nid) as $chnid => $child) { + if ($child->type == $type) { + $links[] = theme("relativity_link",$child->title, "addparent/$child->nid/parent/$parent_nid", $parent_nid, ' '.theme_relativity_trace($child->relativity_trace)); + } + } + } + elseif(count($common_children_reqd)) { + $otherparents = relativity_list_grand_relatives($parent, 'child', 'parent', $common_children_reqd, array($type)); + + foreach($otherparents as $childparent) { + if (node_access("view",$childparent)) { + $links[] = theme("relativity_link",$childparent->title, "addparent/$childparent->nid/parent/$parent_nid", $parent_nid, 'common child required'); + } + } + } + else { + // So, look up all valid nids attached to this parent_node already.. + $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); + $excluded_nids = array(); + // The number of nids returned by this array should be reasonably small. + // It's the number of nodes attached to a given node already. + while($existing_nid = db_fetch_object($result)) { + $excluded_nids[] = $existing_nid->nid; + } + + // if we found some nodes already attached, then use the dreaded NOT IN (..) syntax to get everything but them. + if (count($excluded_nids) > 0) { + $exclusion_list = implode(',', $excluded_nids); + //drupal_set_message("using dreaded NOT IN ($exclusion_list) syntax."); + // maybe this exclusion could be applied manually in the while loop below + $result = db_query('SELECT DISTINCT n.nid FROM {node} n WHERE n.type = \'%s\' AND n.nid NOT IN ('.$exclusion_list.')', $type); + } + else { + $result = db_query('SELECT DISTINCT n.nid FROM {node} n WHERE n.type = \'%s\'', $type); + } + } + + while($child = db_fetch_object($result)) { + $child_node = node_load($child->nid); + if (node_access("view",$child_node)) { + $links[] = theme("relativity_link",$child_node->title, "addparent/$child_node->nid/parent/$parent_nid", $parent_nid); + } + } + + if (count($links)) { + print(theme("page", theme("relativity_bullets", $links))); + } + else { + drupal_set_message(t('There are no available %s items to attach', array('%s'=>node_get_name($type)))); + drupal_goto("node/$parent_nid"); + } +} +/** + * Lists all grandparents or grandchildren of the given node. + * Additionally, allow the direction of search to change directions by having different + * values for $rel_type1 and $rel_type2. + * @param $node the node to start searching from + * @param $rel_type1 Which direction to look ('parent' or 'child') + * @param $rel_type2 Which direction to look beyond children/parents that were found (grand)('parent' or 'child') + * @param $conduit_types array of connecting node types to restrict the search to + * @param $types array of grandparent/grandchild node types to restrict the results to + * @return array of grandparent/grandchild nodes that were found + */ +function relativity_list_grand_relatives($node, $rel_type1='child', $rel_type2='child', $conduit_types = NULL, $types = NULL, $exclude_circular_paths=TRUE) { + //$nodes = array(); + + // List all children/parents. Restrict list to $conduit_types if specified + $conduit_types_sql = ""; + if (is_array($conduit_types) && count($conduit_types)) { + $conduit_types_sql = " AND n.type IN ('".implode("','",$conduit_types)."') "; + } + + if ($rel_type1 == 'parent') { + $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); + } + else { + $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); + } + while($obj = db_fetch_object($result)) { + $relatives[$obj->nid] = $obj->nid; + } + if (is_array($relatives) && count($relatives)) { + // list all children/parents of the $relatives found. Restrict list to $conduit_types if specified + // identify parents and children for exclusion + if ($exclude_circular_paths) { + $all_relatives[$node->nid] = $node->nid; // exclude self + + // parents + $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); + while($obj = db_fetch_object($result)) { + $all_relatives[$obj->nid] = $obj->nid; + } + + // children + $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); + while($obj = db_fetch_object($result)) { + $all_relatives[$obj->nid] = $obj->nid; + } + } + $all_relatives = is_array($all_relatives) ? $all_relatives : array(); + + $types_sql = ""; + if (is_array($types) && count($types)) { + $types_sql = " AND n.type IN ('".implode("','",$types)."') "; + } + + if ($rel_type2 == 'parent') { + $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"); + } + else { + $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"); + } + + while($obj = db_fetch_object($result)) { + if (!($exclude_circular_paths && isset($all_relatives[$obj->nid]))) { + $grand_relatives[$obj->nid] = node_load($obj->nid); + } + } + } + + return is_array($grand_relatives) ? $grand_relatives : array(); +} +/** + * Delete the parent/node relationship + */ +function relativity_unparent_node() { + $output = ""; + $parent_nid = arg(2); + $child_nid = arg(3); + if (is_numeric($parent_nid) && is_numeric($child_nid)) { + $parent = node_load($parent_nid); + $child = node_load($child_nid); + if (relativity_may_unchild($parent, $child)) { + $result = db_query('DELETE FROM {relativity} WHERE nid = %d AND parent_nid = %d', $child_nid, $parent_nid); + drupal_set_message(t('Node relationship removed.')); + } + else { + drupal_set_message(t('Node relationship cannot currently be removed.')); + } + } + else { + drupal_set_message(t('Either that node does not exist or you don\'t have proper privileges to update it')); + } + drupal_goto('node/'.$parent_nid); +} +/** + * Attach a node to it's parent + */ +function relativity_addparent($child_nid="", $parent_nid="") { + if (!$child_nid && !$parent_nid) { + $child_nid = arg(2); + $parent_nid = arg(4); + } + $output = ""; + + db_query('INSERT INTO {relativity} (nid, parent_nid) VALUES (%d, %d)', $child_nid, $parent_nid); + drupal_set_message(t('Node relationship created.')); + drupal_goto('node/'.$parent_nid); +} +function relativity_menu($may_cache) { + global $_menu;// = menu_get_menu(); + $items = array(); + if ($may_cache && variable_get('relativity_enforce_parent_rules', FALSE)) { + // * not working how I wanted it to + // * This needs node_add in node.module to check menu before displaying links + + // iterate through all node types and take over their node/add handlers + foreach(relativity_node_list() as $type){ + if (relativity_requires_parent($type)) { + //drupal_set_message("setting access for type $type"); + $items[] = array('path' => 'node/add/'. $type, 'title' => t('content type requires parent'), + 'callback' => 'node_page', + 'access' => 0,//user_access('administer content'), + 'type' => MENU_CALLBACK, + 'priority' => 1, + 'weight' => 1 + ); + } + } + } + if (!$may_cache) { + if (arg(0) == 'node' && arg(1) == 'add' && array_key_exists(arg(2),relativity_node_list()) && arg(3) == 'parent' && is_numeric(arg(4))) { + // this is my crafty way of creating new nodes as children of parents, and + // not displaying any links to create them otherwise. + $_GET['parent_node'] = arg(4); + $items[] = array('path' => 'node/add/'. arg(2).'/parent/'.arg(4), 'title' => t('create content'), + 'callback' => 'node_page', + 'access' => node_access('create', arg(2)), + 'type' => MENU_CALLBACK, + 'weight' => 1 + ); + } + elseif (arg(0) == 'relativity' && arg(1) == 'listnodes' && array_key_exists(arg(2),relativity_node_list()) && arg(3) == 'parent' && is_numeric(arg(4))) { + $items[] = array('path' => 'relativity/listnodes/'. arg(2).'/parent/'.arg(4), 'title' => t('list %types to attach', array('%type'=>node_get_name(arg(2)))), + 'callback' => 'relativity_list_possible_children', + 'access' => node_access('update', node_load(arg(4))) && user_access("access content"), + 'type' => MENU_CALLBACK + ); + } + elseif (arg(0) == 'relativity' && arg(1) == 'addparent' && is_numeric(arg(2)) && arg(3) == "parent" && is_numeric(arg(4))) { + $items[] = array('path' => 'relativity/addparent/'. arg(2) .'/parent/'. arg(4), 'title' => t('attach node to parent'), + 'callback' => 'relativity_addparent', + 'access' => node_access("view", node_load(arg(2))), + 'type' => MENU_CALLBACK + ); + } + elseif (arg(0) == 'relativity' && arg(1) == 'unparent' && is_numeric(arg(2)) && is_numeric(arg(3))) { + $items[] = array('path' => 'relativity/unparent/'.arg(2).'/'.arg(3), 'title' => t('unparent node'), + 'callback' => 'relativity_unparent_node', + 'access' => node_access("update", node_load(arg(2))), + 'type' => MENU_CALLBACK + ); + } + } + else { + + $items[] = array('path' => 'node/add/relativity', 'title' => t('node relativity query'), + 'access' => user_access('administer content'), + 'weight' => 1 + ); + // add "advanced" tab to admin/settings/relativity + $items[] = array('path' => 'admin/settings/relativity/regular', + 'title' => t('regular settings'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 1, + 'priority' => 1, + 'access' => user_access('administer site configuration') + ); + $items[] = array('path' => 'admin/settings/relativity/display', + 'title' => t('display settings'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 3, + 'priority' => 1, + 'callback' => 'system_site_settings', + 'callback arguments' => array('relativity'), + 'access' => user_access('administer site configuration') + ); + $items[] = array('path' => 'admin/settings/relativity/advanced', + 'title' => t('advanced settings'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 5, + 'priority' => 1, + 'callback' => 'system_site_settings', + 'callback arguments' => array('relativity'), + 'access' => user_access('administer site configuration') + ); + } + return $items; +} +/** + * Implementation of hook_perm(). + */ +function relativity_perm() { + return array('create node relativity queries', 'edit own node relativity queries'); +} +// implementation of hook_settings() +function relativity_settings($tab='') { + //drupal_set_message("hello ==$tab=="); + $group['regular_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Relativity Regular Settings'), + ); + $group['display_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Relativity Display Settings'), + ); + $group['advanced_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Relativity Advanced Settings'), + ); + $label['regular'] = t('Attachment'); + $label['display'] = t('Display'); + $label['advanced'] = t('Attachment'); + + $tab = $tab ? $tab : arg(3); + if (!isset($label[$tab])) { + $tab = 'regular'; + } + $allow_types = relativity_node_list(); + if (count($allow_types) == 0) { + $tab = 'regular'; // this case will be taken care below + } + + switch ($tab) { + case 'advanced': + $group['advanced_settings']['global_options'] = array( + '#type' => 'fieldset', + '#title' => t('Global Attachment Options'), + ); + $group['advanced_settings']['global_options']['relativity_enforce_parent_rules'] = array( + '#type' => 'checkbox', + '#title' => t('Enforce Parental Rules'), + '#return_value' => 1, + '#default_value' => variable_get('relativity_enforce_parent_rules', 0), + '#description' => t('If checked, nodes cannot be created without a parent if they require a parent to exist.'), + ); + + $group['advanced_settings']['global_options']['relativity_nav_types'] = array( + '#type' => 'select', + '#title' => t('Allowable Ancestor Navigation Block Node types'), + '#default_value' => variable_get('relativity_nav_types', 'default'), + '#options' => relativity_node_list('none'), + '#description' => t('What types of nodes are allowed to be used in the navigation block?'), + '#size' => 5, + '#multiple' => TRUE, + ); + // Node Sorting Options + $group['advanced_settings']['sorting_options'] = array( + '#type' => 'fieldset', + '#title' => t('Node Sorting Options'), + ); + // let admins specify sort order for node types + foreach($allow_types as $type => $name) { + if (!$node_order[$type] = variable_get('relativity_node_order_'.$type, FALSE)) { + $node_order[$type] = 1; // if not set, default them to 1 + } + } + // we need a keyed array where numbers are sort order + $sort_options = range(1, count($node_order)); + foreach($sort_options as $opt) { + $options[$opt] = $opt; + } + // display list of node types sorted by ascending sort order + for($i=1; $i <= count($node_order); $i++) { + foreach($allow_types as $type => $name) { + if ($node_order[$type] == $i) { + $group['advanced_settings']['sorting_options']['relativity_node_order_'.$type] = array( + '#type' => 'select', + '#title' => t('Sort Order for %name Nodes', array('%name'=>$name)), + '#default_value' => variable_get('relativity_node_order_'.$type, ""), + '#options' => $options, + ); + } + } + } + foreach(relativity_node_list() as $type=>$name) { + $group['advanced_settings']['node_'.$type.'_options'] = array( + '#type' => 'fieldset', + '#title' => t('%label Options for %name (%type) nodes', array('%name'=>$name, '%type' =>$type, '%label'=>$label[$tab])), + ); + foreach (relativity_node_list() as $chtype=>$chname) { + if (in_array($chtype, variable_get('relativity_type_'.$type, array()))) { + $group['advanced_settings']['node_'.$type.'_options']['relativity_child_ord_'.$type.'_'.$chtype] = array( + '#type' => 'select', + '#title' => t('%chname Child Ordinality for %pname Parents', array('%chname'=>$chname, '%pname'=>$name)), + '#default_value' => variable_get('relativity_child_ord_'.$type.'_'.$chtype, 'any'), + '#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')), + ); + // "Require Common Child of specified type" feature + // find the intersection of 'relativity_type_'.$type and 'relativity_type_'.$chtype (common allowable child types for parent and child) + $common_child_types = array_intersect(variable_get('relativity_type_'.$type, array()), variable_get('relativity_type_'.$chtype, array())); + if (count($common_child_types)) { + foreach($common_child_types as $cchtype) { + $common_types[$cchtype] = node_get_name($cchtype); + } + $group['advanced_settings']['node_'.$type.'_options']['relativity_common_child_'.$type.'_'.$chtype] = array( + '#type' => 'select', + '#title' => t('Require Common Child Node Types for %chname Children', array('%chname'=>$chname)), + '#default_value' => variable_get('relativity_common_child_'.$type.'_'.$chtype, array()), + '#options' => $common_types, + '#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.'), + '#extra' => 0, + '#multiple' => TRUE, + ); + } + // list out possible relativity_queries that could be used to define this relationship type + $possible_queries = NULL; + // 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 ;) + $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\"%'"; + $result = db_query($sql); + while($obj = db_fetch_object($result)) { + $possible_queries[$obj->nid] = $obj->title; + } + if (is_array($possible_queries) && count($possible_queries)) { + $possible_queries[0] = t('none'); + //drupal_set_message($sql." found possible queries: ".print_r($possible_queries,1)); + $group['advanced_settings']['node_'.$type.'_options']['relativity_child_query_'.$type.'_'.$chtype] = array( + '#type' => 'select', + '#title' => t('Use Relativity Query For Listing %chname Children', array('%chname'=>$chname)), + '#default_value' => variable_get('relativity_child_query_'.$type.'_'.$chtype, ''), + '#options' => $possible_queries, + '#description' => t('Use the specified relativity query to identify possible children.'), + ); + } + } + } + } + return $group['advanced_settings']; + case 'display': + $group['display_settings']['global_options'] = array( + '#type' => 'fieldset', + '#title' => t('Global Display Options'), + ); + $group['display_settings']['global_options']['relativity_show_nav_above_body'] = array( + '#type' => 'checkbox', + '#title' => t('Show Ancestor Navigation Above Node Body'), + '#return_value' => 1, + '#default_value' => variable_get('relativity_show_nav_above_body', 0), + '#description' => t('If checked, a listing of the node\'s ancestors will be displayed above each node.'), + ); + $group['display_settings']['global_options']['relativity_show_nav_below_body'] = array( + '#type' => 'checkbox', + '#title' => t('Show Ancestor Navigation Below Node Body'), + '#return_value' => 1, + '#default_value' => variable_get('relativity_show_nav_below_body', 0), + '#description' => t('If checked, a listing of the node\'s ancestors will be displayed below each node.'), + ); + foreach(relativity_node_list() as $type=>$name) { + $group['display_settings']['node_'.$type.'_options'] = array( + '#type' => 'fieldset', + '#title' => t('%label Options for %name (%type) nodes', array('%name'=>$name, '%type' =>$type, '%label'=>$label[$tab])), + ); + $group['display_settings']['node_'.$type.'_options']['relativity_show_parents_'.$type] = array( + '#type' => 'checkbox', + '#title' => t('Show parent nodes'), + '#return_value' => 1, + '#default_value' => variable_get('relativity_show_parents_'.$type, 1), + '#description' => t('If checked, a list of all parents will be displayed with each node.'), + ); + $group['display_settings']['node_'.$type.'_options']['relativity_show_children_'.$type] = array( + '#type' => 'checkbox', + '#title' => t('Show children nodes'), + '#return_value' => 1, + '#default_value' => variable_get('relativity_show_children_'.$type, 1), + '#description' => t('If checked, a list of all children will be displayed with each node.'), + ); + $group['display_settings']['node_'.$type.'_options']['relativity_show_actions_'.$type] = array( + '#type' => 'checkbox', + '#title' => t('Show actions'), + '#return_value' => 1, + '#default_value' => variable_get('relativity_show_actions_'.$type, 1), + '#description' => t('If checked, a list of all possible actions will be displayed with each node.'), + ); + $render_opts = array('title'=>t('title only'),'teaser'=>t('node teaser'),'body'=>t('node body'), 'hide'=>t('hide this child type')); + foreach (relativity_node_list() as $chtype=>$chname) { + if (in_array($chtype, variable_get('relativity_type_'.$type, array()))) { + $group['display_settings']['node_'.$type.'_options']['relativity_render_'.$type.'_'.$chtype] = array( + '#type' => 'select', + '#title' => t('%chname Child Rendering Rule for %pname Parents', array('%chname'=>$chname, '%pname'=>$name)), + '#default_value' => variable_get('relativity_render_'.$type.'_'.$chtype, 'title'), + '#options' => $render_opts, + ); + } + } + } + return $group['display_settings']; + case 'regular': + $group['regular_settings']['description'] = array( + '#type' => 'item', + '#title' => '', + '#value' => t('Below are the general settings for node relationships for each node type. The %display and %advanced pages use the settings on this page, so be sure to save these settings before proceeding to either of them.', array('%display'=>l($label['display'], 'admin/settings/relativity/display'), '%advanced'=>l($label['advanced'], 'admin/settings/relativity/advanced'))), + ); + $group['regular_settings']['global_options'] = array( + '#type' => 'fieldset', + '#title' => t('Global Attachment Options'), + ); + $group['regular_settings']['global_options']['relativity_allow_types'] = array( + '#type' => 'select', + '#title' => t('Which Node types Should Relationships be Managed For?'), + '#default_value' => variable_get('relativity_allow_types', ''), + '#options' => relativity_node_list('none',1), + '#description' => t('What types of nodes should Node Relativity use for configuration? Be sure to include any node type involved (both parent and children types). After saving this, configuration options will appear for all of the node types that you specified.'), + '#size' => 5, + '#multiple' => TRUE, + ); + if (count($allow_types) == 0) { + return $group['regular_settings']; // no node types to manage + } + $group['regular_settings']['global_options']['relativity_parents_label'] = array( + '#type' => 'textfield', + '#title' => t('Label for Parent Nodes links'), + '#default_value' => variable_get('relativity_parents_label', t('Related Parents')), + '#size' => 60, + '#maxlength' => 250, + ); + $group['regular_settings']['global_options']['relativity_children_label'] = array( + '#type' => 'textfield', + '#title' => t('Label for Child Nodes links'), + '#default_value' => variable_get('relativity_children_label', t('Related Items')), + '#size' => 60, + '#maxlength' => 250, + ); + $group['regular_settings']['global_options']['relativity_actions_label'] = array( + '#type' => 'textfield', + '#title' => t('Label for Related Actions links'), + '#default_value' => variable_get('relativity_actions_label', t('Related Actions')), + '#size' => 60, + '#maxlength' => 250, + ); + $group['regular_settings']['global_options']['relativity_ancestors_label'] = array( + '#type' => 'textfield', + '#title' => t('Label for Ancestor Navigation links'), + '#default_value' => variable_get('relativity_ancestors_label', t('Related Ancestors')), + '#size' => 60, + '#maxlength' => 250, + ); + foreach (relativity_node_list() as $type=>$name) { + $group['regular_settings']['node_'.$type.'_options'] = array( + '#type' => 'fieldset', + '#title' => t('%label Options for %name (%type) nodes', array('%name'=>$name, '%type' =>$type, '%label'=>$label[$tab])), + ); + $group['regular_settings']['node_'.$type.'_options']['relativity_parent_ord_'.$type] = array( + '#type' => 'select', + '#title' => t('Parental Ordinality'), + '#default_value' => variable_get('relativity_parent_ord_'.$type, 'any'), + '#options' => array('any'=>t('any'), 'one'=>t('one'), 'none'=>t('none'), 'one or more'=>t('one or more')), + '#description' => t('How many parents can this node type have?'), + ); + $group['regular_settings']['node_'.$type.'_options']['relativity_type_'.$type] = array( + '#type' => 'select', + '#title' => t('Allowable Child Node types'), + '#default_value' => variable_get('relativity_type_'.$type, 'default'), + '#options' => relativity_node_list('none'), + '#description' => t('What types of nodes are allowed to be attached to this type?'), + '#size' => 5, + '#multiple' => TRUE, + ); + } + return $group['regular_settings']; + } // end of switch($tab) +} +/** + * Implementation of hook_block(). + * + * Generates navigation block to quickly jump to any of this node's ancestors. + */ +function relativity_block($op = 'list', $delta = 0) { + // The $op parameter determines what piece of information is being requested. + if ($op == 'list') { + // If $op is "list", we just need to return a list of block descriptions. This + // is used to provide a list of possible blocks to the administrator. + $blocks[0]['info'] = t('Navigate through a node\'s ancestors'); + return $blocks; + } + else if ($op == 'view') { + // see if we're viewing a node + if (arg(0) == "node" && is_numeric(arg(1))) { + // see if it's a valid node + $node = node_load(arg(1)); + if (is_object($node) && node_access('view', $node)) { + $ancestors = relativity_load_ancestors($node); + if (is_array($ancestors) && count($ancestors) > 0) { + $block['subject'] = $node->title; + $block['content'] = theme('relativity_ancestors', $node, $ancestors); + } + } + } + return $block; + } +} +/** + * Generate an array of ancestors for the given node. + * This will keep looking for more ancestors until a circular path was found, + * if a node has multiple or no parents at all. + * If $load_multi_parent is true, the first multi-parent result found will be placed + * in the output array as an array of nodes. Otherwise, all array elements will be nodes. + */ +function relativity_load_ancestors($node, $load_multi_parent=1) { + $search_max = 5; // make this a settings variable someday + if (is_numeric($node->nid)) { + $ancestors = array(); + $nids = array(); + $search_nid = $node->nid; + $cnt = 0; + $relativity_nav_types = variable_get('relativity_nav_types', array()); + do { + $keep_going = 0; + $result = db_query("SELECT parent_nid from {relativity} where nid=%d", $search_nid); + $parent_count = db_num_rows($result); + if ($parent_count == 1 && $obj = db_fetch_object($result)) { + // make sure we're not pursuing a circular path and that we stop at $search_max parents + if (!in_array($obj->parent_nid, $nids) && $cnt++ < $search_max) { + //drupal_set_message("unshifting node ".$obj->parent_nid." onto the ancestors array"); + $parent_node = node_load($obj->parent_nid); + if (node_access('view', $parent_node) && in_array($parent_node->type, $relativity_nav_types)) { + array_unshift($ancestors, $parent_node); + $nids[] = $obj->parent_nid; + $search_nid = $obj->parent_nid; + $keep_going = 1; + } + } + } + elseif($parent_count > 1 && $load_multi_parent && $cnt++ < $search_max) { + //drupal_set_message("found parent_count == $parent_count, about to add array to ancestors array"); + // perhaps upon encountering multiple parents, it would suffice to print them + // all out at the same level and not follow any of them up, but still allow + // the user to navigate up whatever hierarchy they want to. + $siblings = array(); + while($obj = db_fetch_object($result)) { + // make an array of nodes to display at this level + $parent_node = node_load($obj->parent_nid); + if (!in_array($obj->parent_nid, $nids) && node_access('view', $parent_node) && in_array($parent_node->type, $relativity_nav_types)) { + array_unshift($siblings, $parent_node); + $nids[] = $obj->parent_nid; + $search_nid = $obj->parent_nid; // only used when single result is rendered. + } + } + if (count($siblings) > 0) { + array_unshift($ancestors, $siblings); + } + if (count($siblings) == 1) { + $keep_going = 1; + } + } + } while($keep_going); + return $ancestors; + } +} +/** + * Executes a relativity_query node using the default target_nid or one specified + * as an optional parameter. + * + * @param $querynode a relativity_query type node containing query info + * @param $target_nid optionally specify a starting node + * @return an array of nodes indexed by nid that were found while searching. + */ +function relativity_execute_query($querynode, $keywords=NULL, $target_nid=NULL) { + if (!$target_nid) { + $target_nid = $querynode->target_nid; + } + $search_results = relativity_list_relatives($target_nid, $querynode->follow_parents, $querynode->follow_children, $querynode->recursion_depth, $querynode->unique_types, $querynode->max_results, $querynode->search_types, $querynode->end_pts, $querynode->avoid_pts, $querynode->options, $querynode->search_algorithm); + if ($keywords) { + $search_results = relativity_filter_keywords($search_results, $keywords); + } + //drupal_set_message("running query for ".print_r($querynode,1)." found ".count($search_results)." results"); + return $search_results; +} +/** + * A wrapper function that identifies the correct search algorithm function and + * invokes it. + * + * @param $nid the node id of the node to search from + * @param $follow_parent_paths if true, traverse parent paths of the graph + * @param $follow_child_paths if true, traverse child paths of the graph + * @param $recurse Specify the maximum neighbor distance to search through (-1 for $max_recursion) + * @param $unique_node_types if true, only follow a node path if no node currently exists in the results of that node's type + * @param $max_results discontinue searching when this many results have been found + * @param $search_types array of node types that are the only types allowed in the results. + * @param $end_types array of node types that are considered an end point, with no searching going beyond that node. + * @param $avoid_types array of node types that will not be included in the results and will not be searched through. + * @param $search_algorithm which search algorithm to use (currently, only 'dfs', or Depth-First Search is supported). + * + * @return an array of nodes indexed by nid that were found while searching. + */ +function relativity_list_relatives($nid, $follow_parent_paths=1, $follow_child_paths=1, $recurse=0, $unique_node_types=0, $max_results=50, $search_types = NULL, $end_types = NULL, $avoid_types = NULL, $options=NULL, $search_algorithm='dfs') { + $search_algorithm = $search_algorithm ? $search_algorithm : 'dfs'; + $search_func = 'relativity_list_relatives_'.$search_algorithm; + if (function_exists($search_func)) { + return $search_func($nid, $follow_parent_paths, $follow_child_paths, $recurse, $unique_node_types, $max_results, $search_types, $end_types, $avoid_types, $options); + } + drupal_set_message(t('Search algorithm \'%algo\' not supported', array('%algo'=>$search_algorithm))); + return array(); +} +/** + * Navigate DiGraph of nodes upwards (and downwards, too if $follow_child_paths=1) + * and return an array of distinct nids whose values are the node type. + + * logic: + * start with a nid. + * if follow_parent_paths, find all parents of this nid and add them to the nids array + * if the nid is already in the nodes array, skip it + * otherwise, recurse with the parent nodes that were just found + * if follow_child_paths, find all children and add them to the nids array + * if the nid is already in the nodes array, skip it + * otherwise, recurse with the child nodes that were found. + * return the nodes array. + * + * @param $nid the node id of the node to search from + * @param $follow_parent_paths if true, traverse parent paths of the graph + * @param $follow_child_paths if true, traverse child paths of the graph + * @param $recurse Specify the maximum neighbor distance to search through (-1 for $max_recursion) + * @param $unique_node_types if true, only follow a node path if no node currently exists in the results of that node's type + * @param $end_types array of node types that are considered an end point, with no searching going beyond that node. + * @param $avoid_types array of node types that will not be included in the results and will not be searched through. + * @param $init if true, reset static variables to initial state - typically true when called but false upon recursion. + * + * @return an array of nodes indexed by nid that were found while searching. + */ +function relativity_list_relatives_dfs($nid, $follow_parent_paths=1, $follow_child_paths=1, $recurse=0, $unique_node_types=0, $max_results=50, $search_types = NULL, $end_types = NULL, $avoid_types = NULL, $options=NULL, $init=1) { + global $user; + static $nodes = array(); + static $node_types = array(); + static $visited = array(); + static $trace = array(); + static $depth = 0; + $search_max = 500; //sanity check + $max_recursion = 500; //sanity check + $search_sql = array(); + // NOTE: this may be dangerous here, as we're importing items into the namespace + // of this function. Current known items: do_trace, my_nodes_only, display_format + if (is_array($options)) { + extract($options); + } + if ($init) { + $nodes = array(); + $node_types = array(); + $trace = array(); + $depth = 0; + if ($recurse == -1) { + $recurse = $max_recursion; + } + if (!$max_results) { + $max_results = 50; + } + $visited = array(); + //drupal_set_message("init: list_relatives_dfs($nid, follow_parent_paths: $follow_parent_paths, follow_child_paths: $follow_child_paths, recurse: $recurse, unique_node_types: $unique_node_types, max_results: $max_results, search_types: $search_types(".count($search_types)."), end_types: $end_types(".count($end_types)."), avoid_types: $avoid_types(".count($avoid_types)."), options: ".print_r($options,1).", init: $init, depth: $depth"); + } + + // don't traverse the same node multiple times. + if (in_array($nid, $visited)) { + //drupal_set_message("already visited this node ($nid)"); + return $nodes; + } + + //drupal_set_message("list_relatives_dfs($nid, $follow_parent_paths, $follow_child_paths, $recurse, $unique_node_types, $max_results, $search_types(".count($search_types)."), $end_types(".count($end_types)."), $avoid_types(".count($avoid_types)."), $init), options: ".print_r($options,1).", depth: $depth"); + + if ($follow_parent_paths) { + $search_sql['parent'] = "SELECT parent_nid as the_nid from {relativity} where nid=%d"; + } + + if ($follow_child_paths) { + $search_sql['child'] = "SELECT nid as the_nid from {relativity} where parent_nid=%d"; + } + + if (!is_array($search_types) || !count($search_types)) { + $search_types = relativity_node_list(); + } + + if (!is_array($end_types)) { + $end_types = array(); + } + + if (!is_array($avoid_types)) { + $avoid_types = array(); + } + + if (count($search_types)) { + $restrict_search_types = TRUE; + } + else { + $restrict_search_types = FALSE; + } + + if (is_numeric($nid)) { + $visited[] = $nid; // log the fact that we've been here + $cnt = 0; + // + // there's an odd case where the starting node needs to be included in the results + // This code only gets executed during the initial pass + if ($init) { + $relative = node_load($nid); + $relative->relativity_depth = $depth; + //drupal_set_message("init loop, considering starting node: $nid..." . print_r($relative, 1)); + if ($relative->type && (!$my_nodes_only || $user->uid == $relative->uid) && !in_array($relative->type, $avoid_types) && node_access('view', $relative) && !($unique_node_types && isset($node_types[$relative->type]))) { + $relative->recursion_depth = $depth; + + if ($restrict_search_types && in_array($relative->type, $search_types)) { + //drupal_set_message("starting node matched all the search rules, so add it to the results"); + $nodes[$obj->the_nid] = $relative; + $node_types[$relative->type] = 1; + } + } + else { + //drupal_set_message("starting node did NOT match the search filter, so don't add it to the results - currently size ".count($nodes)); + } + } + + $depth++; + + //drupal_set_message("following nid $nid..."); + + foreach($search_sql as $direction=>$sql) { + $result = db_query($sql, $nid); + while($obj = db_fetch_object($result)) { + + // we've reached our maximum result set size, bail out now + if (count($nodes) >= $max_results) { + return $nodes; + } + // quick efficiency break here: + // if unique_node_types are required, and $nodes is the same size as $search_types, we're done + if ($unique_node_types && count($nodes) >= count($search_types)) { + return $nodes; + } + + // make sure we're not pursuing a circular path and that we stop at $search_max parents + if ($obj->the_nid && !isset($nodes[$obj->the_nid]) && $cnt++ < $search_max) { + $relative = node_load($obj->the_nid); + $relative->relativity_depth = $depth; + if ($relative->type && (!$my_nodes_only || $user->uid == $relative->uid) && !in_array($relative->type, $avoid_types) && node_access('view', $relative) && !($unique_node_types && isset($node_types[$relative->type]))) { + $relative->recursion_depth = $depth; + //$nodes[$obj->the_nid] = $relative->type.": ".$relative->title; // for debugging + if ($do_trace) { + array_push($trace, $nid); + //drupal_set_message("list_relatives_dfs: depth: $depth, trace: ".print_r($trace, 1)); + } + if ($restrict_search_types && in_array($relative->type, $search_types)) { + if ($do_trace) { + $relative->relativity_trace = $trace; + } + $nodes[$obj->the_nid] = $relative; + $node_types[$relative->type] = 1; + } + else { + //drupal_set_message("skipping $relative->nid (type: $relative->type) as it was not in search_types: " . print_r($search_types,1)); + } + + if ($depth < $recurse && !in_array($relative->type, $end_types)) { + //drupal_set_message("recursing for parent ".$obj->the_nid); + relativity_list_relatives_dfs($obj->the_nid, $follow_parent_paths, $follow_child_paths, $recurse, $unique_node_types, $max_results, $search_types, $end_types, $avoid_types, $options, 0); + } + if ($do_trace) { + array_pop($trace); + } + } + } + } + } + $depth--; // step back up the search path + } + return $nodes; +} +function relativity_list_children($nid, $recurse=0, $unique_node_types=0, $max_results=50, $search_types = NULL, $end_types = NULL, $avoid_types = NULL, $do_trace=NULL) { + return relativity_list_relatives($nid, 0, 1, $recurse, $unique_node_types, $max_results, $search_types, $end_types, $avoid_types, $do_trace); +} +function relativity_list_parents($nid, $recurse=0, $unique_node_types=0, $max_results=50, $search_types = NULL, $end_types = NULL, $avoid_types = NULL, $do_trace=NULL) { + return relativity_list_relatives($nid, 1, 0, $recurse, $unique_node_types, $max_results, $search_types, $end_types, $avoid_types, $do_trace); +} +/** + * Determines if the specified parent node may add a child of type $type. + * @return true if possible, false otherwise. + */ +function relativity_may_add_child($parent, $type) { + $common_children_reqd = variable_get('relativity_common_child_'.$parent->type.'_'.$type, array()); + if (count($common_children_reqd)) { + // 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 + + // lookup all existing children and see if any of them are on the common_children_reqd list + $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); + while($child = db_fetch_object($result)) { + $children[] = $child->nid; + } + // no common children defined for the parent, reject the request + if (!$children || !is_array($children) || !count($children)) { + return false; + } + + // now, see if any of the children that were found have a parent of type $type + $children = implode(',', $children); + $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); + $count = db_result($result); + if (!$count) { + return false; + } + } + $ord = variable_get('relativity_child_ord_'.$parent->type.'_'.$type, 'any'); + if ($ord == 'any') { + return true; + } + else { + // look up how many children this parent currently has of the specified type + $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)); + if ($res->cnt >= $ord) { + return false; + } + else { + return true; + } + } +} +/** + * Takes an array of nodes (keyed with nid) and a keyword string and checks to + * see if any of them match search words in the search_index table + */ +function relativity_filter_keywords($search_results, $keyword_string) { + //$keywords = preg_split("/[^A-Za-z0-9_\-\+\$\@\#\']+/", $keyword_string); // split strings on any non alpha-num char + $keywords = preg_split("/[\s,]+/", $keyword_string); // split strings on any whitespace or comma + + // no valid keywords were specified, so just disregard keyword search filtering + if (!count($keywords)) { + return $search_results; + } + + if (is_array($search_results) && count($search_results)) { + $placeholders = array(); + foreach($keywords as $k=>$val) { + // Remove punctuation/special characters (same rule as update_index()). + $keywords[$k] = preg_replace("'(!|%|,|:|;|\(|\)|\&|\"|\'|\.|-|\/|\?|\\\)'", '', $val); + $kplaceholders[] = "'%s'"; + } + unset($search_results['']); // for some reason, there's an empty key in here that's messing things up + + $nids = implode(',', array_keys($search_results)); + $results = db_query("SELECT lno AS nid, SUM(count) AS rating FROM {search_index} WHERE type='node' AND lno IN ($nids) AND word IN (".implode(',', $kplaceholders).") GROUP BY lno ORDER BY rating DESC;", $keywords); + while($obj = db_fetch_object($results)) { + $node = $search_results[$obj->nid]; + $node->relativity_keyword_rating = $obj->rating; + $filtered_results[$node->nid] = $node; + } + } + if ($filtered_results) { + return $filtered_results; + } + return array(); +} +/** + * implementation of hook_access + * + * New usage: + * Without the flexinode hack pointing back here for access privileges, I've + * removed the old code and now manage access to relativity_query node types. + * + * Old usage: + * Iterate through all node types and disable the creation of nodes that require + * a parent unless a $_GET['parent_node'] param is present. + * This doesn't work using current system. This is being called explicitly by flexinode_access. + * + */ +function relativity_access($op, $node) { + global $user; + switch($op) { + case 'create': + if ($node == 'relativity' && user_access('create node relativity queries')) { + return TRUE; + } + elseif(relativity_requires_parent($node) && !isset($_GET['parent_node'])) { + //drupal_set_message("relativity_access denying access here for node type $node, parent=$parent_nid"); + return FALSE; + } + + break; + + case 'update': + case 'delete': + if ($node->uid == $user->uid) { + return user_access('edit own node relativity queries'); + } + break; + + } +} +function relativity_access_denied() { + print theme('page', message_access(), t('Access denied')); +} +/** + * Convenience function for determining if a node requires a parent to exist. + */ +function relativity_requires_parent($node) { + if (is_object($node)) { + $type = $node->type; + } + else { + $type = $node; + } + if (variable_get("relativity_parent_ord_$type", "") == 'one' || + variable_get("relativity_parent_ord_$type", "") == 'one or more') { + return true; + } + return false; +} +/** + * Convenience function for determining if a node may have more than one parent. + */ +function relativity_multi_parent($node) { + if (is_object($node)) { + $type = $node->type; + } + else { + $type = $node; + } + if (variable_get("relativity_parent_ord_$type", "") == 'any' || + variable_get("relativity_parent_ord_$type", "") == 'one or more') { + return true; + } + return false; +} +/** + * If this node is required to connect a series of nodes together, return FALSE + */ +function relativity_may_unchild($parent, $child) { + + if (!node_access("update", $parent)) { + return FALSE; + } + + // check all possible child types for this parent + foreach(node_get_types() as $type=>$name) { + $conduit_types = variable_get('relativity_common_child_'.$parent->type.'_'.$type, array()); + if (in_array($child->type, $conduit_types)) { + // make sure no child nodes of type $type exist with $child as a child, as they would require $child here to exist. + $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); + if (db_num_rows($result) > 0) { + return FALSE; + } + } + } + return TRUE; +} +/** + * See if current user may create a *new* child of type $child_type to a parent of type $parent_type + */ +function relativity_may_attach_new_child_type($parent_type, $child_type) { + if (node_access('create', $child_type)) { + // make sure relationship is still valid + if (in_array($child_type, variable_get('relativity_type_'.$parent_type, array()))) { + // make sure relatinship doesn't require a common child + if (!variable_get('relativity_common_child_'.$parent_type.'_'.$child_type, FALSE)) { + return TRUE; + } + } + else { + return TRUE; + } + } + return FALSE; +} +/** + * Deletes all relationships involved with the specified node + */ +function relativity_delete_relationships($node) { + // look to see if this is a required common child that is currently holding relationships together + foreach(node_get_types() as $ptype=>$pname) { + foreach(node_get_types() as $chtype=>$chname) { + // make sure that the parent/child relationship is still valid + if (in_array($chtype, variable_get('relativity_type_'.$ptype, array()))) { + + $conduit_types = variable_get('relativity_common_child_'.$ptype.'_'.$chtype, array()); + if (in_array($node->type, $conduit_types)) { + //drupal_set_message("deleting conduit node of type $node->type. See if there are any dependent $chtype relatives that are children of $ptype nodes"); + // find all children of type $chtype of this node's parents of type $ptype + $dependent_children = relativity_list_grand_relatives($node, 'parent', 'child', array($ptype), array($chtype), FALSE); + if (is_array($dependent_children) && count($dependent_children)) { + $dependent_children[$node->nid] = $node->nid; // include self in children filter + + // find their parents of type $ptype + $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); + while($obj = db_fetch_object($result)) { + // if this potentially dependent parent also has $node as a child, delete it + 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))) { + $dependent_parents[] = $obj->nid; + } + } + if (is_array($dependent_parents) && count($dependent_parents)) { + drupal_set_message(t('Removing dependent children (%children) from dependent parents (%parents)', array('%children'=>implode(',', array_keys($dependent_children)), '%parents'=>implode(',', $dependent_parents)))); + // delete any relationships that require these $dependent_parents to have these $dependent_children + db_query("DELETE FROM {relativity} WHERE parent_nid IN (".implode(',', $dependent_parents).") AND nid IN (".implode(',', array_keys($dependent_children)).")"); + } + } + } + } + } + } + // clear out all relationships directly involving this node + db_query('DELETE FROM {relativity} WHERE nid = %d OR parent_nid = %d', $node->nid, $node->nid); +} +/** + * Implementation of hook_form_alter(). + */ +function relativity_form_alter($form_id, &$form) { + + $type = (isset($form['type']) && isset($form['type']['#value'])) ? $form['type']['#value'] : NULL; + $node = isset($form['#node']) ? $form['#node'] : NULL; + switch ($form_id) { + case $type .'_node_form': + + // add relativity links to the edit tab of a node + // format the output and return it as markup for the $form element + if ($node->parent_node) { + if (is_array($node->parent_node)) { + $form_add['parent_node'] = array( + '#type' => 'hidden', + '#value' => implode(',',$node->parent_node), + ); + } else { + $form_add['parent_node'] = array( + '#type' => 'hidden', + '#value' => $node->parent_node, + ); + } + $output = drupal_get_form('relativity_nodeapi', $form_add); + } + if ($types = variable_get('relativity_type_'. $node->type, FALSE)) { + $output .= theme("relativity_links", $types, $node, 'edit'); + } + return $form[] = array('#type' => 'markup', '#value' => $output); + break; + } +} +/** + * Implementation of hook_nodeapi(). + * + * We will implement several node API operations here. This hook allows us to + * act on all major node operations, so we can manage our additional data + * appropriately. + */ +function relativity_nodeapi(&$node, $op, $teaser, $page) { + + if ($_GET['parent_node']) { + $node->parent_node = $_GET['parent_node'] + 0; + } + elseif($_POST['parent_node']) { + $node->parent_node = $_POST['parent_node'] + 0; + } + switch ($op) { + case 'validate': + if ($node->nid && ($_GET['parent_node'] || $_POST['parent_node'])) { + $parents = explode(',', $node->parent_node); + foreach($parents as $parent_nid) { + $parent = node_load($parent_nid); + if (!$parent || !in_array($parent->type, variable_get('relativity_type_'. $node->type, array()))) { + form_set_error('relativity_type'.$node->type, t('You\'re not allowed to create this type of attachment.')); + } + } + } + break; + case 'insert': + if ($node->nid && $node->parent_node) { + if (is_array($node->parent_node)) { + foreach($node->parent_node as $parent_node) { + db_query('INSERT INTO {relativity} (nid, parent_nid) VALUES (%d, %d)', $node->nid, $parent_node); + } + } + else { + db_query('INSERT INTO {relativity} (nid, parent_nid) VALUES (%d, %d)', $node->nid, $node->parent_node); + } + } + break; + // remove all relationships to or from this node + case 'delete': + if ($node->parent_node) { + relativity_delete_relationships($node); + } + break; + // if there is a parent to this node, create a node property named "parent_node" + // which contains the nid of its parent. If there are more than one parent, + // this will be an array of such nids. + case 'load': + $result = db_query('SELECT parent_nid FROM {relativity} WHERE nid = %d', $node->nid); + if (db_num_rows($result) > 1) { + $parent_node = array(); + while($object = db_fetch_object($result)) { + $parent_node[] = $object->parent_nid; + } + } + elseif(db_num_rows($result) == 1) { + $object = db_fetch_object($result); + $parent_node = $object->parent_nid; + } + if ($parent_node) { + return array('parent_node' => $parent_node); + } + else { + return array(); + } + break; + case 'view': + $output = ""; + if (variable_get('relativity_show_parents_'.$node->type, TRUE)) { + $output .= theme('relativity_show_parents', $node); + } + + if (variable_get('relativity_show_children_'.$node->type, TRUE)) { + $output .= theme('relativity_show_children', $node); + } + + if ($types = variable_get('relativity_type_'. $node->type, FALSE)) { + $output .= theme("relativity_links", $types, $node, 'view'); + } + $node->body .= $output; + if (variable_get('relativity_show_nav_above_body', FALSE)) { + $nav = relativity_block('view'); + $node->body = $nav['content'] ? theme('fieldset', array('#title' => variable_get('relativity_ancestors_label', t('Related Ancestors')), '#children' => $nav['content'])) . $node->body : $node->body; + } + if (variable_get('relativity_show_nav_below_body', FALSE)) { + $nav = relativity_block('view'); + $node->body = $node->body . $nav['content'] ? theme('fieldset', array('#title' => variable_get('relativity_ancestors_label', t('Related Ancestors')), '#children' => $nav['content'])) : ''; + } + break; + } +} +// This section handles Node Relativity queries +// queries have the following fields: +// nid, title, target_nid, follow_parents, follow_children, recursion_depth, end_pts, avoid_pts +/** + * implementation of hook_node_info() + */ +function relativity_node_info() { + return array('relativity_query' => array('name' => t('node relativity query'), 'base' => 'relativity')); +} +/** + * Implementation of hook_form(). + */ +function relativity_form(&$node, &$error) { + $output = ''; + $max_search=100; + $form['title'] = array( + '#type' => 'textfield', + '#title' => t('Title'), + '#required' => TRUE, + '#default_value' => $node->title, + '#weight' => -5, + ); + if ($_GET['edit']['target_nid'] && is_numeric($_GET['edit']['target_nid'])) { + $form['target_nid'] = array( + '#type' => 'hidden', + '#value' => $_GET['edit']['target_nid'], + ); + } else { + $form['target_nid'] = array( + '#type' => 'textfield', + '#title' => t('Specify the node id (nid) of the starting node'), + '#default_value' => $node->target_nid, + '#size' => 10, + '#maxlength' => 25, + ); + } + // Now we define the form elements specific to our node type. + $form['follow_parents'] = array( + '#type' => 'checkbox', + '#title' => t('Follow Parent Paths'), + '#return_value' => 1, + '#default_value' => $node->follow_parents, + '#description' => t('If checked, the search engine will follow parent-oriented paths while searching for results.'), + ); + $form['follow_children'] = array( + '#type' => 'checkbox', + '#title' => t('Follow Child Paths'), + '#return_value' => 1, + '#default_value' => $node->follow_children, + '#description' => t('If checked, the search engine will follow child-oriented paths while searching for results.'), + ); + $form['unique_types'] = array( + '#type' => 'checkbox', + '#title' => t('Require Unique Types'), + '#return_value' => 1, + '#default_value' => $node->unique_types, + '#description' => t('If checked, the search engine will return only the first instance of each node type found.'), + ); + $options = array(); + // $options[-1] = t('use max'); // for some reason, this is not working (and I don't care enough to fix it ;) + foreach(range(1,$max_search) as $i) { + $options[$i] = $i; + } + $form['max_results'] = array( + '#type' => 'select', + '#title' => t('Max Search Results'), + '#default_value' => isset($node->max_results) ? $node->max_results : '', + '#options' => $options, + '#description' => t('The maximum number of results to return.'), + ); + $form['recursion_depth'] = array( + '#type' => 'select', + '#title' => t('Search Distance'), + '#default_value' => isset($node->recursion_depth) ? $node->recursion_depth : 10, + '#options' => $options, + '#description' => t('How many levels away from the starting node should be searched (recursion depth)?'), + ); + $form['search_algorithm'] = array( + '#type' => 'select', + '#title' => t('Search Algorithm'), + '#default_value' => $node->search_algorithm, + '#options' => array('dfs' => t('Depth-First Search')), + '#description' => t('Specify the search algorithm to use for searching the network of nodes (currently, only DFS is supported).'), + ); + $form['search_types'] = array( + '#type' => 'select', + '#title' => t('Search Node types'), + '#default_value' => $node->search_types, + '#options' => relativity_node_list(), + '#description' => t('Specify which node types to search for. Only include nodes of this type in the results (leave blank for all).'), + '#multiple' => TRUE, + ); + $form['end_pts'] = array( + '#type' => 'select', + '#title' => t('End Point Node types'), + '#default_value' => $node->end_pts, + '#options' => relativity_node_list(), + '#description' => t('Specify which node types to stop searching beyond, but include in the results.'), + '#multiple' => TRUE, + ); + $form['avoid_pts'] = array( + '#type' => 'select', + '#title' => t('Avoid Node types'), + '#default_value' => $node->avoid_pts, + '#options' => relativity_node_list(), + '#description' => t('Specify which node types to avoid completely. These will not be included in the results.'), + '#multiple' => TRUE, + ); + $form['display_format'] = array( + '#type' => 'select', + '#title' => t('Display Format'), + '#default_value' => $node->options['display_format'] ? $node->options['display_format'] : 'title', + '#options' => array('title'=>t('title only'),'teaser'=>t('node teaser'),'body'=>t('node body')), + '#description' => t('Specify the way in which the results should be formatted.'), + ); + $form['do_trace'] = array( + '#type' => 'checkbox', + '#title' => t('Perform Search Trace'), + '#return_value' => 1, + '#default_value' => $node->options['do_trace'], + '#description' => t('If checked, the search engine will log each node that was found leading up to each result.'), + ); + $form['my_nodes_only'] = array( + '#type' => 'checkbox', + '#title' => t('Only Search My Nodes'), + '#return_value' => 1, + '#default_value' => $node->options['my_nodes_only'], + '#description' => t('If checked, the search engine will only consider noded created by the current user when it is searching for nodes.'), + ); + $form['show_search_node_field'] = array( + '#type' => 'checkbox', + '#title' => t('Display Search Node Form Field'), + '#return_value' => 1, + '#default_value' => $node->options['show_search_node_field'], + '#description' => t('If checked, the search page will display a form field allowing viewers to specify an alternate starting node.'), + ); + $form['show_keyword_field'] = array( + '#type' => 'checkbox', + '#title' => t('Display Search Keyword Form Field'), + '#return_value' => 1, + '#default_value' => $node->options['show_keyword_field'], + '#description' => t('If checked, the search page will display a form field allowing viewers to specify a keyword to filter the results on.'), + ); + return $form; +} +/** + * Implementation of hook_validate(). + * + * This hook lets + * us ensure that the user entered an appropriate value before we try + * inserting anything into the database. + */ +function relativity_validate($node) { + if ($_POST['edit'] && !$node->follow_parents && !$node->follow_children) { + form_set_error('follow_parents', t('Either "Follow Parent Paths" or "Follow Child Paths" must be selected.')); + } +} +/** + * Implementation of hook_submit(). + * + * This hook lets + * us ensure that the user entered an appropriate value before we try + * inserting anything into the database. + */ +function relativity_submit(&$node) { + if ($_POST['edit'] && !$node->follow_parents && !$node->follow_children) { + form_set_error('follow_parents', t('Either "Follow Parent Paths" or "Follow Child Paths" must be selected.')); + } + if (!$node->search_algorithm) { + $node->search_algorithm = 'dfs'; + } + if (!$node->max_results) { + $node->max_results = 50; + } + // squiggle around the additional db fields + if ($node->display_format) { + $node->options['display_format'] = $node->display_format; + } + if ($node->do_trace) { + $node->options['do_trace'] = $node->do_trace; + } + if ($node->my_nodes_only) { + $node->options['my_nodes_only'] = $node->my_nodes_only; + } + if ($node->show_search_node_field) { + $node->options['show_search_node_field'] = $node->show_search_node_field; + } + if ($node->show_keyword_field) { + $node->options['show_keyword_field'] = $node->show_keyword_field; + } +} +/** + * Implementation of hook_insert(). + */ +function relativity_insert($node) { + db_query("INSERT INTO {relativity_query} (nid, target_nid, follow_parents, follow_children, recursion_depth, unique_types, max_results, search_algorithm, search_types, end_pts, avoid_pts, options) VALUES (%d, %d, %d, %d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')", $node->nid, $node->target_nid, $node->follow_parents, $node->follow_children, $node->recursion_depth, $node->unique_types, $node->max_results, $node->search_algorithm, serialize($node->search_types), serialize($node->end_pts), serialize($node->avoid_pts), serialize($node->options)); +} +/** + * Implementation of hook_update(). + */ +function relativity_update($node) { + db_query("UPDATE {relativity_query} SET target_nid = %d, follow_parents = %d, follow_children = %d, recursion_depth = %d, unique_types = %d, max_results = %d, search_algorithm = '%s', search_types = '%s', end_pts = '%s', avoid_pts = '%s', options = '%s' WHERE nid = %d", $node->target_nid, $node->follow_parents, $node->follow_children, $node->recursion_depth, $node->unique_types, $node->max_results, $node->search_algorithm, serialize($node->search_types), serialize($node->end_pts), serialize($node->avoid_pts), serialize($node->options), $node->nid); +} +/** + * Implementation of hook_delete(). + * + * When a node is deleted, we need to clean up related tables. + */ +function relativity_delete($node) { + db_query('DELETE FROM {relativity_query} WHERE nid = %d', $node->nid); +} +/** + * Implementation of hook_load(). + * + * Now that we've defined how to manage the node data in the database, we + * need to tell Drupal how to get the node back out. This hook is called + * every time a node is loaded, and allows us to do some loading of our own. + */ +function relativity_load($node) { + $additions = db_fetch_object(db_query('SELECT target_nid, follow_parents, follow_children, recursion_depth, unique_types, max_results, search_algorithm, search_types, end_pts, avoid_pts, options FROM {relativity_query} WHERE nid = %d', $node->nid)); + $additions->search_types = unserialize($additions->search_types); + $additions->end_pts = unserialize($additions->end_pts); + $additions->avoid_pts = unserialize($additions->avoid_pts); + $additions->options = unserialize($additions->options); + return $additions; +} +/** + * Implementation of hook_view(). + * + * The query node is executed and results are themed. + */ +function relativity_view(&$node, $teaser = FALSE, $page = FALSE) { + $node->teaser = theme('relativity_query_form',$node); + $node->body = $node->teaser; + if ($_GET['edit']['target_nid']) { + $node->body .= theme('relativity_results', relativity_execute_query($node, $_GET['edit']['keywords'], $_GET['edit']['target_nid']), t('Search Results'), $node->options); + } + elseif($node->target_nid) { + $node->body .= theme('relativity_results', relativity_execute_query($node, $_GET['edit']['keywords']), t('Search Results'), $node->options); + } + +} +/** + * Sorts an array of node types based on admin specification + * @param an array of node type names to sort or empty to use _node_list() + * @return an array of sorted node type names, unindexed + */ +function relativity_sort_types($types=NULL) { + static $sorted_node_list; + if (!is_array($sorted_node_list)) { + foreach(range(1, count(node_get_types())) as $idx) { + foreach(node_get_types() as $type=>$name) { + if (variable_get('relativity_node_order_'.$type, 1) == $idx) { + $sorted_node_list[] = $type; + } + } + } + } + if (!$types) { + // cut it short and return the presorted static node list + return $sorted_node_list; + } + + // discard garbage + if (!is_array($types)) { + return array(); + } + + // nothing to sort if there's less than 2 elements + if (count($types) < 2) { + return $types; + } + foreach($sorted_node_list as $type) { + if (in_array($type, $types)) { + $sorted_types[] = $type; + } + } + + return is_array($sorted_types) ? $sorted_types : array(); +} +/** + * Convenience function to load just a node's type and title for display purposes + */ +function relativity_load_nodeinfo($nid) { + static $node_data = array(); + //drupal_set_message("loading node info for nid $nid"); + if (!$node_data[$nid]) { + $obj = db_fetch_object(db_query('SELECT n.type, n.title FROM {node} n WHERE nid=%d', $nid)); + $node_data[$nid] = array('type'=>$obj->type, 'title'=>$obj->title); + } + return $node_data[$nid]; +} +function theme_relativity_show_parents($node) { + $output = ''; + // load all nodes associated with this one as the parent nid and show links to all of them. + $result = db_query('SELECT parent_nid as pid FROM {relativity} WHERE nid = %d', $node->nid); + while($parent = db_fetch_object($result)) { + $parent_nodes[] = node_load($parent->pid); + } + + // sort output by node type + if (is_array($parent_nodes)) { + foreach(relativity_sort_types() as $type) { + foreach($parent_nodes as $parent_node) { + if ($parent_node->type == $type and user_access('view '. $parent_node->type)) { + $links .= "'; + } + } + } + } + if ($links) { + $output .= theme('fieldset', array('#title' => variable_get('relativity_parents_label', t('Related Ancestors')), '#children' => $links)); + } + + return $output; +} +function theme_relativity_show_children($node) { + $output = ''; + + // load all nodes associated with this one as the parent nid and show links to all of them. + $result = db_query('SELECT nid FROM {relativity} WHERE parent_nid = %d', $node->nid); + while($child = db_fetch_object($result)) { + $child_nodes[] = node_load($child->nid); + } + // sort output by node type + if (is_array($child_nodes)) { + foreach(relativity_sort_types() as $type) { + foreach($child_nodes as $child_node) { + if ($child_node->type == $type and user_access('view '. $child_node->type)) { + switch(variable_get('relativity_render_'.$node->type.'_'.$child_node->type, 'title')) { + case 'title': + $links .= "'; + break; + case 'teaser': + $links .= "'; + break; + case 'body': + $links .= "'; + break; + } + } + } + } + } + if ($links) { + $output .= theme('fieldset', array('#title' => variable_get('relativity_children_label', t('Related Items')), '#children' => $links)); + } + return $output; +} +/** + * themes the output for the query search form using options within the node + */ +function theme_relativity_query_form($node) { + if (!$node) { + return ''; + } + if (is_array($node->options)) { + extract($node->options); + } + if ($show_search_node_field) { + $form['target_nid'] = array( + '#type' => 'textfield', + '#title' => t('Specify the node id (nid) of the starting node'), + '#default_value' => $_GET['edit']['target_nid']? $_GET['edit']['target_nid'] : $node->target_nid, + '#size' => 10, + '#maxlength' => 25, + ); + } + if ($show_keyword_field) { + $form['keywords'] = array( + '#type' => 'textfield', + '#title' => t('Search Words (optional)'), + '#default_value' => $_GET['edit']['keywords'], + '#size' => 20, + '#maxlength' => 255, + ); + } + if ($show_search_node_field || $show_keyword_field) { + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Search'), + ); + $form['#method'] = 'GET'; + $form['#action'] = url("node/$node->nid"); + return drupal_get_form('relativity_query_form', $form); + } + return ''; +} +function theme_relativity_trace($trace="") { + $output = ""; + if (!$trace || !is_array($trace)) { + return ""; + } + foreach($trace as $nid) { + $info = relativity_load_nodeinfo($nid); // returns nid, type and title for node + $links[] = l($info['title'], 'node/'.$nid); + } + return "[".implode(" >> ", $links)."]"; +} +function theme_relativity_results($nodes, $title, $options) { + if (is_array($options)) { + extract($options); + } + $display_format = $display_format ? $display_format : 'title'; + //drupal_set_message("nodes: ".print_r(array_keys($nodes))); + // sort output by node type + foreach(relativity_sort_types() as $type) { + foreach($nodes as $nid=>$node) { + if ($node->type == $type) { + switch($display_format) { + case 'title': + $number = module_invoke('comment', 'num_all', $node->nid); + $items[] = "
".l(node_get_name($node->type).": ".$node->title, 'node/'. $node->nid, $number ? array('title' => format_plural($number, '1 comment', '%count comments')) : '') .' '. t('(Distance: %depth)', array('%depth'=>$node->recursion_depth)).theme_relativity_trace($node->relativity_trace).'
'; + break; + case 'teaser': + $items[] = "
".theme('fieldset', + array('#title' => node_get_name($node->type). t(' (Distance: %depth)', array('%depth'=>$node->recursion_depth)).": ", + '#children' => theme_relativity_trace($node->relativity_trace).node_view($node, TRUE) + )).'
'; + break; + case 'body': + $items[] = "
" . theme('fieldset', + array('#title' => node_get_name($node->type). t(' (Distance: %depth)', array('%depth'=>$node->recursion_depth)).": ", + '#children' => theme_relativity_trace($node->relativity_trace).node_view($node, FALSE), + )).'
'; + break; + } + } + } + } + drupal_set_title($title); + return $items? implode("\n", $items) : ""; +} +/** + * This function themes the output of the $types array. It should + * print a list of links to attach a node. + */ +function theme_relativity_links($types, $parent, $op='view') { + $parent_nid = $parent->nid; + + if (!is_array($types) || count($types) == 0 || !$types[0] || !$parent_nid) { + return; + } + $types = relativity_sort_types($types); + + foreach($types as $type) { + if (relativity_may_add_child($parent, $type)) { + $type_name = node_get_name($type); + if (!$type_name) { + $type_name = $type; + } + + $may_create = relativity_may_attach_new_child_type($parent->type, $type); + // only offer to let user create new child if user has 'create' access and common child relationship isn't required + if ($may_create && node_access('update', $parent)) { + $output .= l(t('create new %type', array('%type'=>$type_name)), "node/add/$type/parent/$parent_nid") . "
\n"; + } + // only show link to attach existing node when the potential child doesn't require a parent. + if ((!relativity_requires_parent($type) || relativity_multi_parent($type)) && node_access('update', $parent)) { + // Use 'create' privilege to change up the link text + if ($may_create) { + $ltitle = t('attach existing %type', array('%type'=>$type_name)); + } + else { + $ltitle = t('attach %type', array('%type'=>$type_name)); + } + $output .= l($ltitle, "relativity/listnodes/$type/parent/$parent_nid") . "
\n"; + } + } + } + if ($op == 'edit') { + // show attachment removal links + $result = db_query('SELECT nid FROM {relativity} WHERE parent_nid = %d', $parent_nid); + $output .= '
'; + while($child = db_fetch_object($result)) { + $child_node = node_load($child->nid); + // make sure that no dependencies exist that would require this node to be attached + if (relativity_may_unchild($parent, $child_node)) { + $removables[] = $child_node; + } + } + if (is_array($removables)) { + // sort output by node type + foreach(relativity_sort_types() as $type) { + foreach($removables as $child_node) { + if ($child_node->type == $type) { + $output .= theme("relativity_link", '['.t('Remove ').node_get_name($child_node->type).": ".$child_node->title.']', 'unparent/'.$parent_nid.'/'.$child_node->nid); + } + } + } + } + } + if ($output) { + return theme('fieldset', array('#title' => variable_get('relativity_actions_label', t('Related Actions')), '#children' => $output)); + } +} +/** + * This function themes the output of the $types array. It should + * print a list of links to attach a node. + */ +function theme_relativity_link($title, $target, $parent_nid=0, $extra='') { + $output = ''; + return $output; +} +function theme_relativity_ancestors($node, $ancestors) { + $content = ""; + $indent = 0; + foreach($ancestors as $ancestor) { + if (is_array($ancestor)) { + foreach($ancestor as $sibling) { + $content .= theme("relativity_ancestor", $sibling, $indent); + } + $indent++; + } + else { + $content .= theme("relativity_ancestor", $ancestor, $indent++); + } + } + return $content; +} +function theme_relativity_ancestor($ancestor, $indent=0) { + if (!$ancestor->type||!$ancestor->title||!$ancestor->nid) { + return ''; + } + $content .= "
"; + $content .= l("".node_get_name($ancestor->type).":
".$ancestor->title, 'node/'.$ancestor->nid); + $content .= "
"; + return $content; +} +function theme_relativity_bullets($items) { + return ""; +}