'. t('The project module makes special use of the taxonomy (category) system. A special vocabulary, %vocabulary_name, has been created automatically.', array('%vocabulary_name' => $vocabulary->name)) .'

'; $text .= '

'. t('To take full advantage of project categorization, add at least two levels of terms to this vocabulary. The first level will be the basic project types, e.g., "Modules", "Themes", "Translations".') .'

'; $text .= '

'. t('Subterms of each of these types will be the categories that users can select to classify the projects. For example, "Modules" might have sub-terms including "Mail" and "XML".') .'

'; if ($vocab_link) { $text .= '

'. t('Use the vocabulary administration page to view and add terms.', array('@taxonomy-admin' => url('admin/content/taxonomy/'. $vid))) .'

'; } return $text; } /** * Implementation of hook_block(). */ function project_block($op = 'list', $delta = 0, $edit = array()) { if ($op == 'list') { $blocks[0]['info'] = t('Project navigation'); if (module_exists('search')) { $blocks[1]['info'] = t('Project search'); } return $blocks; } else if ($op == 'view') { switch ($delta) { case 0: $block = array( 'subject' => t('Project navigation'), 'content' => drupal_get_form('project_quick_navigate_form'), ); break; case 1: if (user_access('search content')) { $block = array( 'subject' => t('Search projects'), 'content' => drupal_get_form('project_search_block_form', 'project_project'), ); } break; } return $block; } elseif ($op == 'configure' && $delta == 1) { $form = array(); $form['help_text'] = array( '#type' => 'textfield', '#title' => t('Help text'), '#description' => t('Enter optional help text to display in the block.'), '#default_value' => variable_get('project_search_block_help_text', ''), ); return $form; } elseif ($op == 'save' && $delta == 1) { variable_set('project_search_block_help_text', $edit['help_text']); } } function project_search_block_form($node_type) { $form = search_box('project_search_block_form_'. $node_type); $form['node_type'] = array( '#type' => 'value', '#value' => $node_type, ); $form['#base'] = 'project_search_block_form'; $help_text = variable_get('project_search_block_help_text', ''); if (!empty($help_text)) { $element = 'project_search_block_form_'. $node_type .'_keys'; $form[$element]['#description'] = check_plain($help_text); } return $form; } function project_search_block_form_submit($form_id, $form_values) { return 'search/node/type:'. $form_values['node_type'] .' '. trim($form_values[$form_id .'_'. $form_values['node_type'] .'_keys']); } function project_node_info() { return array( 'project_project' => array( 'name' => t('Project'), 'module' => 'project_project', 'description' => t('A project is something a group is working on. It can optionally have issue tracking, integration with revision control systems, releases, and so on.'), ), ); } function project_perm() { $perms = array( 'administer projects', 'maintain projects', 'access projects', 'access own projects', ); return $perms; } /** * Implementation of hook_db_rewrite_sql(). * * Both project (and, if enabled, project_issue) provide some permissions that * restrict access to viewing projects (and issues). For these permissions to * be globally honored by the system, db_rewrite_sql() has to check what * permissions the current user has and restrict query results accordingly. * * If a user has 'access projects' and 'access project issues', they have full * access, so there's nothing to re-write. If they have 'access own projects' * and/or 'access own project issues', the resulting query should JOIN on * {node} and ensure that the node type is not project_project or * project_issue, or that the owner of the node is the current user. If they * do not have any access to either, then we can just restrict the query based * on the {node}.type column. * * @see project_perm() * @see project_issue_perm() * @see project_find_alias() */ function project_db_rewrite_sql($query, $primary_table, $primary_field) { if ($primary_field == 'nid') { $return = array(); $access_projects = user_access('access projects'); $admin_projects = user_access('administer projects'); if (module_exists('project_issue')) { $access_issues = user_access('access project issues'); $access_own_issues = user_access('access own project issues'); } else { $access_issues = TRUE; $access_own_issues = TRUE; } if ($admin_projects || ($access_projects && $access_issues)) { // User has full access, nothing to re-write. return; } else { // We have to make sure {node} is in the query and know the alias. if ($primary_table == 'n' || $primary_table == 'node') { // Great, it's the primary table and we already know the alias. $alias = $primary_table; } else { // Look for {node} in the query. if (!($alias = project_find_alias($query, 'node'))) { // Not already in the query, JOIN it. $return['join'] = "INNER JOIN {node} pn ON pn.nid = ". $primary_table .'.nid'; $alias = 'pn'; } } } // At this point, we know we have to restrict something, and we know what // the {node} table's alias is in the query. $where = array(); global $user; $uid = $user->uid; // Some node types will be restriced by our query, but we want to allow // every other node type. We keep track of the types we're handling, and // at the end we'll add a clause to ignore/allow all other types. $restricted_types = array(); if (!$access_projects) { $restricted_types[] = "'project_project'"; if (user_access('access own projects')) { $where[] = "($alias.type = 'project_project' AND $alias.uid = $uid)"; } } if (module_exists('project_issue') && !$access_issues) { $restricted_types[] = "'project_issue'"; if ($access_own_issues) { $where[] = "($alias.type = 'project_issue' AND $alias.uid = $uid)"; } } // If the type is not one of the restricted project* ones, allow access. $where[] = "($alias.type NOT IN (". implode(', ', $restricted_types) ."))"; // Now that we have all our WHERE clauses, we just OR them all together. $return['where'] = implode(' OR ', $where); return $return; } } /** * Find the table alias for a table in a query. * * @param $query * The query to examine for the alias. * @param * $table The table to examine for the alias. * @return * The alias if it exists, or the name of the table if it's present but not * aliased, or FALSE if the table is not present. * * @see project_db_rewrite_sql() */ function project_find_alias($query, $table) { // See if {$table} is already in the query. $match = array(); // This regexp handles many cases: // - {$table} can be either in FROM or in a JOIN clause // - Query might end immediately after {$table} // - Might not have a table alias for {$table} $pattern = "@.*(FROM|JOIN)\s+\{$table\}\s*(\S+)?@"; if (preg_match($pattern, $query, $match)) { $keywords = '@(ON|INNER|LEFT|RIGHT|WHERE|ORDER|GROUP|HAVING|LIMIT)@'; if (!isset($match[2]) || preg_match($keywords, $match[2])) { // No alias found, just use $table. $alias = $table; } else { // Alias found. $alias = $match[2]; } } else { // Table not in query. $alias = FALSE; } return $alias; } /** * Callback for the main settings page. */ function project_settings_form() { $sort_methods = drupal_map_assoc(array_keys(module_invoke_all('project_sort_methods', 'methods'))); if (project_use_taxonomy()) { // For now, date-based browsing doesn't work once you disable // taxonomy (only because the code involved is rather complicated // and needs to be majorly refactored and cleaned up). $form['project_sort_method'] = array( '#type' => 'radios', '#title' => t('Default sort option'), '#default_value' => variable_get('project_sort_method', 'category'), '#options' => $sort_methods, '#description' => t('Default sorting option to use on the overview page'), ); $form['sort_methods'] = array( '#type' => 'fieldset', '#title' => t('Enabled sorting options'), '#description' => t('Each type of project on your site will have its own project browsing page at %link. Each browsing page can be configured for different browsing options (by name, by date, etc). The settings below determine which browsing methods are available for each project type.', array('%link' => url('project', NULL, NULL, TRUE) . '/[type]')), ); $tree = taxonomy_get_tree(_project_get_vid(), 0 , -1, 1); foreach ($tree as $term) { $form['sort_methods']['project_sort_method_used_' . $term->tid] = array( '#type' => 'checkboxes', '#title' => $term->name, '#default_value' => array_filter(variable_get('project_sort_method_used_'. $term->tid, array_keys($sort_methods))), '#options' => $sort_methods, ); } } $form['project_browse_nodes'] = array( '#type' => 'select', '#title' => t('Number of projects to list in paged browsing'), '#default_value' => variable_get('project_browse_nodes', 30), '#options' => drupal_map_assoc(array(5, 10, 15, 20, 25, 30, 35, 40, 45, 50)), '#description' => t('The default maximum number of projects to list when browsing lists, e.g., by category.') ); return system_settings_form($form); } /** * Returns the vocabulary id for projects. */ function _project_get_vid() { $vid = variable_get('project_vocabulary', ''); if (empty($vid)) { // Check to see if a project module vocabulary exists. $vid = db_result(db_query("SELECT vid FROM {vocabulary} WHERE module='%s'", 'project')); if (!$vid) { $edit = array( 'name' => t('Project types'), 'multiple' => 1, 'hierarchy' => 1, 'relations' => 0, 'module' => 'project', 'nodes' => array('project_project' => 1), ); // If there is already a vocabulary assigned to 'project_project' nodes, use it. $vocabularies = taxonomy_get_vocabularies('project_project'); if (count($vocabularies)) { $vocabulary = reset($vocabularies); $edit['vid'] = $vocabulary->vid; } taxonomy_save_vocabulary($edit); $vid = $edit['vid']; } variable_set('project_vocabulary', $vid); } return $vid; } /** * Implementation of hook_term_path(). */ function project_term_path($term) { // The path must include the first-level term name for this term. $tree = taxonomy_get_tree(_project_get_vid()); $parents = taxonomy_get_parents_all($term->tid); foreach($parents as $parent) { $ancestors[] = $parent->tid; } foreach ($tree as $t) { if (in_array($t->tid, $ancestors) && $t->depth == 0) { $termname = $t->name; if ($term->name == $termname) { return "project/$termname"; } break; } } return "project/$termname/category/$term->tid"; } /** * Implementation of hook_taxonomy(). */ function project_taxonomy($op, $type, $array = NULL) { if ($op == 'delete' && $type == 'vocabulary' && $array['vid'] == _project_get_vid()) { variable_del('project_vocabulary'); } elseif ($type == 'term' && $array['vid'] == _project_get_vid()) { menu_rebuild(); } } function project_menu($may_cache) { $items = array(); global $user; if ($may_cache) { // User pages: $access_all = user_access('access projects'); $access_own = user_access('access own projects'); $access_admin = user_access('administer projects'); $access = $access_all || $access_own || $access_admin; $items[] = array('path' => 'project', 'title' => t('Projects'), 'callback' => 'project_page_overview', 'access' => $access, 'type' => MENU_NORMAL_ITEM); $items[] = array('path' => 'project/autocomplete', 'title' => t('Autocomplete project'), 'callback' => 'project_autocomplete', 'access' => $access, 'type' => MENU_CALLBACK); // Project browsing pages if (project_use_taxonomy()) { $default_sort = variable_get('project_sort_method', 'category'); $sort_methods = module_invoke_all('project_sort_methods', 'methods'); $terms = taxonomy_get_tree(_project_get_vid()); foreach ($terms as $i => $term) { // Only use the first-level terms. if ($term->depth == 0) { $items[] = array('path' => 'project/'. $term->name, 'title' => $term->name, 'access' => $access, 'type' => MENU_NORMAL_ITEM, 'weight' => $term->weight, 'callback arguments' => array($term->name)); $j = 0; $term_methods = array_keys(array_filter(variable_get('project_sort_method_used_'. $term->tid, $sort_methods))); foreach ($term_methods as $sort_method) { $items[] = array('path' => 'project/' . $term->name . '/'. $sort_method, 'title' => t('Browse by !sort_method', array('!sort_method' => $sort_method)), 'access' => $access, // It's a default task if a) sort method matches default, or b) default sort isn't // in the list of sort methods, and it's the first tab. 'type' => (($sort_method == $default_sort || (($j == 0) && !in_array($default_sort, $term_methods))) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK), 'weight' => ($sort_method == $default_sort) ? -10 : $j, 'callback arguments' => array($term->name, $sort_method)); $j++; } } } } // Developers $items[] = array('path' => 'project/developers', 'title' => t('Developers'), 'callback' => 'project_developers', 'access' => $access, 'type' => MENU_CALLBACK); // CVS messages: $items[] = array('path' => 'project/cvs', 'title' => t('CVS'), 'callback' => 'project_cvs', 'access' => $access, 'type' => MENU_CALLBACK); // Administration pages $items[] = array( 'path' => 'admin/project', 'title' => t('Project administration'), 'description' => t('Administrative interface for project management and related modules.'), 'callback' => 'system_admin_menu_block_page', 'access' => $access_admin, 'type' => MENU_NORMAL_ITEM, ); $items[] = array( 'path' => 'admin/project/project-settings', 'title' => t('Project settings'), 'description' => t('Configure the behavior and appearance of the project browsing pages and other settings for the Project module.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('project_settings_form'), 'access' => $access_admin, 'type' => MENU_NORMAL_ITEM, ); } else { drupal_add_css(drupal_get_path('module', 'project') .'/project.css'); if (arg(0) == 'node' && is_numeric(arg(1))) { $node = node_load(arg(1)); if ($node->type == 'project_project' && node_access('update', $node) && (module_exists('project_issue') || module_exists('project_release'))) { $items[] = array( 'path' => 'node/'. arg(1) .'/edit/project', 'title' => t('Project'), 'callback' => 'node_page', 'weight' => -5, 'type' => MENU_DEFAULT_LOCAL_TASK, ); } } } return $items; } function project_check_admin_access($project, $cvs_access = NULL) { global $user; if (empty($user->uid)) { return FALSE; } $project_obj = is_numeric($project) ? node_load($project) : $project; if (!isset($project_obj) || $project_obj->type != 'project_project') { return FALSE; } if (user_access('administer projects')) { return TRUE; } // If $cvs_access is not defined, check to make sure the user has cvs access // and that the user's cvs account is approved. if (project_use_cvs($project_obj) && !isset($cvs_access)) { if (db_num_rows(db_query("SELECT * FROM {cvs_accounts} WHERE uid = %d AND status = %d", $user->uid, CVS_APPROVED))) { $cvs_access = TRUE; } else { $cvs_access = FALSE; } } if (user_access('maintain projects')) { if ($user->uid == $project_obj->uid) { return TRUE; } if (project_use_cvs($project_obj) && $cvs_access) { if (db_num_rows(db_query("SELECT * FROM {cvs_project_maintainers} WHERE uid = %d AND nid = %d", $user->uid, $project_obj->nid))) { return TRUE; } } } return FALSE; } /** * Implementation of form_alter. This removes the work of * taxonomy.module's form_alter() so we can do our own taxonomy * selection. */ function project_form_alter($form_id, &$form) { if ($form_id == 'project_project_node_form') { $vid = _project_get_vid(); if (isset($form['taxonomy'][$vid])) { unset($form['taxonomy'][$vid]); } // If there are no children elements, we should unset the entire // thing so we don't end up with an empty fieldset. if (!element_children($form['taxonomy'])) { unset($form['taxonomy']); } // If the form has an element for specifying a URL alias, we want // to alter it, since we're just going to automatically override // the specified value. if (isset($form['path'])) { $url_alias = $form['path']['path']['#default_value']; if (empty($url_alias)) { unset($form['path']); } else { unset($form['path']['path']); $form['path']['value'] = array( '#prefix' => '
', '#value' => t('Automatically generated path alias: %url', array('%url' => $url_alias)), '#suffix' => '
', ); } } } } /** * hook_nodeapi() implementation. This just decides what type of node * is being passed, and calls the appropriate type-specific hook. * * @see project_project_nodeapi(). */ function project_nodeapi(&$node, $op, $arg) { switch ($node->type) { case 'project_project': project_project_nodeapi($node, $op, $arg); break; } } function project_page() { global $user; switch (arg(1)) { default: project_page_overview(); break; } } function project_page_overview($termname = NULL, $sort_method = NULL) { global $form_values; project_project_set_breadcrumb(); $sort_methods = module_invoke_all('project_sort_methods', 'methods'); $output = ''; $active_tids = array(); if (module_exists('project_release') && variable_get('project_release_browse_versions', 0)) { $version_form = drupal_get_form('project_release_version_filter_form'); $output .= $version_form; // Read in requested version, if any. $version = isset($_SESSION['project_version']) ? $_SESSION['project_version'] : variable_get('project_release_overview', -1); $active_tids = project_release_compatibility_list(); } // If browsing by taxonomy, only fetch projects for this term. if (project_use_taxonomy()) { $vid = _project_get_vid(); if ($termname) { $type = db_fetch_object(db_query(db_rewrite_sql("SELECT t.tid, t.* FROM {term_data} t INNER JOIN {term_hierarchy} th ON t.tid = th.tid WHERE t.vid = %d AND th.parent = 0 AND t.name = '%s'", 't', 'tid'), $vid, $termname)); // Bad project category passed, return page not found. if (!$type) { return drupal_not_found(); } if (!$sort_method) { // For the default, we use the overall default if enabled for this term, and if not we use the first enabled method. $term_methods = array_filter(variable_get('project_sort_method_used_' . $type->tid, array_keys($sort_methods))); $sort_method = in_array(variable_get('project_sort_method', 'category'), $term_methods) ? variable_get('project_sort_method', 'category') : array_shift($term_methods); } if ($type->description) { $output .= '

' . filter_xss($type->description) . '

'; } $module = $sort_methods[$sort_method]; // Bad sort method passed, return page not found. if (!isset($module)) { return drupal_not_found(); } if (module_invoke($module, 'project_sort_methods', 'set term', $sort_method)) { $tree = taxonomy_get_tree(_project_get_vid(), $type->tid); $terms = array(); if ($tree) { $tids = array(); // Make sure the API compatability term contains one or more terms // (not including the term with key of -1, which is always // automatically added to the array of terms. If not then browsing // by version is not possible. project_release_compatibility_list() // only returns active tids if browsing by version is enabled. if (count($active_tids) > 1) { $version_params = array(); if ($version != -1) { $version_where = 'tr.tid = %d'; $version_params[] = $version; } else { $placeholders = array(); foreach ($active_tids as $tid => $api_term) { $placeholders[] = '%d'; $version_params[] = $tid; } $version_where = 'tr.tid IN ('. implode(',', $placeholders) .')'; } // Find all terms associated with the requested version. $result = db_query("SELECT tp.tid, COUNT(DISTINCT(n.nid)) AS count FROM {term_node} tp INNER JOIN {project_release_nodes} p ON tp.nid = p.pid INNER JOIN {node} n ON n.nid = p.pid INNER JOIN {term_node} tr ON tr.nid = p.nid WHERE $version_where AND (n.status = 1) GROUP BY tp.tid", $version_params); $tids = array(); while ($item = db_fetch_object($result)) { $tids[$item->tid] = $item->count; } } foreach ($tree as $cterm) { if (!module_exists('project_release') || (!variable_get('project_release_browse_versions', 0) || ($version == -1) || array_key_exists($cterm->tid, $tids))) { if ($tids[$cterm->tid]) { $cterm->count = $tids[$cterm->tid]; } else { // We don't use taxonomy_term_count_nodes() because it includes child terms' counts. $result = db_query(db_rewrite_sql('SELECT t.tid, COUNT(n.nid) AS c FROM {term_node} t INNER JOIN {node} n ON t.nid = n.nid WHERE n.status = 1 AND t.tid = %d GROUP BY t.tid'), $cterm->tid); $term = db_fetch_object($result); $cterm->count = $term->c; } $terms[] = $cterm; } } } // Look for a specific category term. if ($arg = arg(3)) { // Bad term, return page not found. if (!($term = taxonomy_get_term($arg))) { return drupal_not_found(); } } } // The sorting method is not supported by the module, so any term passed is invalid. // Return page not found if one is found. elseif (arg(3)) { return drupal_not_found(); } // Set the default elements that will be used to construct the SQL statement. $sql_elements = project_empty_query(); $sql_elements['fields']['pieces'] = array( 'DISTINCT(n.nid)', 'n.title', 'n.sticky', 'n.type', 'nr.teaser', 'nr.format', ); $sql_elements['from']['pieces'] = array( '{node} n ' ); $sql_elements['joins']['pieces'] = array( 'INNER JOIN {node_revisions} nr ON n.vid = nr.vid', 'INNER JOIN {project_projects} p ON n.nid = p.nid', 'LEFT JOIN {term_node} r ON n.nid = r.nid' ); $sql_elements['wheres']['pieces'] = array( 'n.status = 1', 'r.tid = %d' ); $sql_elements['parameters']['pieces'] = array( isset($term) ? $term->tid : $type->tid ); // If the site has enabled issue tracking via the project_issue // module, we want to add 1 more field and JOIN to our query so // we can provide the link for "Bugs and feature requests"... if (module_exists('project_issue')) { $sql_elements['fields']['pieces'][] = 'pip.issues'; $sql_elements['joins']['pieces'][] = 'INNER JOIN {project_issue_projects} pip ON n.nid = pip.nid'; } // Only the 'pieces' are returned; the prefix and glue elements remain unchanged. $sql_settings = module_invoke($module, 'project_sort_methods', 'sql_settings', $sort_method); if (empty($sql_settings)) { $sql_settings = array(); } $active_tids = array(); if (module_exists('project_release')) { $project_release_sql = array( 'joins' => array('INNER JOIN {project_release_projects} prp ON n.nid = prp.nid'), 'wheres' => array('prp.releases = 1'), ); $sql_settings = array_merge_recursive($sql_settings, $project_release_sql); $active_tids = project_release_compatibility_list(); } // Make sure the API compatability term contains one or more terms // (not including the term with key of -1, which is always // automatically added to the array of terms. If not then browsing // by version is not possible. project_release_compatibility_list() // only returns active tids if browsing by version is enabled. if (count($active_tids) > 1) { $release_settings = array( 'fields' => array('prn.file_path', 'prn.version', 'MAX(prn.file_date) AS changed', 'COUNT(*) AS release_count'), 'group_bys' => array('n.nid'), ); $release_settings['joins'] = array( 'INNER JOIN {project_release_nodes} prn ON n.nid = prn.pid', 'INNER JOIN {term_node} tr ON tr.nid = prn.nid', 'INNER JOIN {node} rn ON rn.nid = prn.nid', ); $release_settings['wheres'][] = 'rn.status = 1'; if ($version != -1) { $release_settings['joins'][] = 'INNER JOIN {project_release_supported_versions} prsv ON prsv.nid = p.nid AND prsv.tid = tr.tid'; $release_settings['wheres'][] = 'tr.tid = %d'; $release_settings['wheres'][] = 'prsv.supported = 1'; $release_settings['parameters'][] = $version; } else { $placeholders = array(); foreach ($active_tids as $tid => $compatibility_term) { $placeholders[] = '%d'; $release_settings['parameters'][] = $tid; } $where = 'tr.tid IN ('. implode(',', $placeholders) .')'; $release_settings['wheres'][] = $where; // We need to grab the uid for the project node, so // that node_access() will work properly without a full // node_load() when we're generating the download table in // project_release_table(). $release_settings['fields'][] = 'n.uid'; } $sql_settings = array_merge_recursive($sql_settings, $release_settings); } else { $release_settings = array( 'fields' => array('n.changed AS changed'), ); $sql_settings = array_merge_recursive($sql_settings, $release_settings); } // Merge in $sql_elements if (!empty($sql_settings)) { foreach(array_keys($sql_settings) as $key) { $sql_elements[$key]['pieces'] = array_merge($sql_elements[$key]['pieces'], $sql_settings[$key]); $sql_elements[$key]['pieces'] = array_unique($sql_elements[$key]['pieces']); } } $parameters = $sql_elements['parameters']['pieces']; $sql = project_build_query($sql_elements); $pager = module_invoke($module, 'project_sort_methods', 'pager', $sort_method); if ($pager) { $first_field = array_shift($sql_elements['fields']['pieces']); if (count($sql_elements['group_bys']['pieces'])) { $first_field = 'DISTINCT(' . $sql_elements['group_bys']['pieces'][0] . ')'; $sql_elements['group_bys']['pieces'] = array(); } $sql_elements['fields']['pieces'] = array("COUNT($first_field)"); // ORDER BY can screw up COUNT(DISTINCT), and we don't care // about the order for the count query. unset($sql_elements['order_bys']); $count_query = project_build_query($sql_elements); $result = pager_query($sql, variable_get('project_browse_nodes', 30), 0, $count_query, $parameters); } else { $result = db_query($sql, $parameters); } // If we've just switched versions, we may have a term requested but no matching projects. // In that case, we returned an uncollapsed fieldset. if (module_invoke($module, 'project_sort_methods', 'set term', $sort_method)) { $output .= theme('fieldset', array('#title' => t('Categories'), '#collapsible' => TRUE, '#collapsed' => (arg(3) && db_num_rows($result)) ? TRUE : FALSE, '#children' => theme('project_term_list', $terms, "project/$termname/$sort_method"))); if (!arg(3) || !db_num_rows($result)) { return $output; } drupal_set_title(t('@project_type: %category', array('@project_type' => $type->name, '%category' => $term->name))); } else { drupal_set_title(check_plain($type->name)); } } // If taxonomy is enabled but no termname is selected, show a list of terms to choose from. else { $tree = taxonomy_get_tree($vid, 0, -1, 1); $items = array(); foreach ($tree as $term) { $items[] = theme('project_type', $term); } drupal_set_title(t('Project types')); return theme('item_list', $items); } } // If taxonomy is not enabled, fetch all projects else { // Any terms passed without taxonomy module enabled are bogus -- return page not found. if (isset($termname)) { return drupal_not_found(); } // If the site has enabled issue tracking via the project_issue // module, we want to add 1 more field and JOIN to our query so // we can provide the link for "Bugs and feature requests"... if (module_exists('project_issue')) { $ISSUES = ', pip.issues'; $ISSUE_JOIN ='INNER JOIN {project_issue_projects} pip ON n.nid = pip.nid'; } $result = db_query(db_rewrite_sql("SELECT n.nid, n.title, nr.teaser, nr.format$ISSUES FROM {node} n INNER JOIN {node_revisions} nr ON n.vid = nr.vid $ISSUE_JOIN WHERE n.status = 1 AND n.type = 'project_project' ORDER BY n.title ASC")); } $projects = ''; $class = 'even'; while ($project = db_fetch_object($result)) { $project->body = check_markup($project->teaser, $project->format, FALSE); if (module_exists('taxonomy')) { $project->taxonomy = taxonomy_node_get_terms($project->nid); if (isset($type)) { // Hide the top-level project type term from the links, but add it to // the $project object for theme_project_summary() to use if it wants. unset($project->taxonomy[$type->tid]); $project->term = $termname; } $project->terms = taxonomy_link('taxonomy terms', $project); } // Make sure we have the latest release if (isset($project->release_count) && $project->release_count > 1 && $version != -1) { $latest = db_fetch_object(db_query_range("SELECT file_path, version, file_date FROM {project_release_nodes} p INNER JOIN {node} n ON p.nid = n.nid INNER JOIN {term_node} t ON p.nid = t.nid LEFT JOIN {project_release_supported_versions} prsv ON p.pid = prsv.nid AND prsv.tid = t.tid AND prsv.major = p.version_major WHERE p.pid = %d AND t.tid = %d AND prsv.supported = 1 AND n.status = 1 ORDER BY prsv.nid DESC, p.rebuild ASC, p.version_major ASC, p.version_minor DESC, p.version_patch DESC, p.file_date DESC", $project->nid, $version, 0, 1)); $project->file_path = $latest->file_path; $project->version = $latest->version; } $project->links = array(); if ($version != -1) { if ($project->file_path) { $project->links['project_download'] = theme('project_release_download_link', $project->file_path, t('Download'), 'array'); } } else { $project->download_table = theme('project_release_table_overview', $project, 'recommended', 'all', t('Version'), FALSE); } $project->links['project_more_info'] = array( 'title' => t('Find out more'), 'href' => "node/$project->nid", ); if ($project->issues) { $project->links['project_issues'] = array( 'title' => t('Bugs and feature requests'), 'href' => "project/issues/$project->nid", ); } if (module_invoke($module, 'project_sort_methods', 'group by date', $sort_method) && $date = _project_date($project->changed)) { $projects .= "

$date

"; } $project->class = ($class == 'even') ? 'odd': 'even'; $projects .= theme('project_summary', $project); $class = $project->class; } $output .= '
' . $projects . '
'; if (!empty($pager)) { $output .= theme('pager', NULL, variable_get('project_browse_nodes', 30)); } return $output; } /** * Build a SQL statment from a structure array. * * @param $sql_elements * An array with the following keys: * 'fields', 'from', 'joins', 'wheres', 'group_bys', 'order_bys', * 'parameters' * Each value is an array with the following keys: * 'prefix', 'glue', 'pieces' * @return * SQL string. */ function project_build_query($sql_elements) { $sql = ''; foreach ($sql_elements as $key => $sql_element) { if ($key != 'parameters' && count($sql_element['pieces'])) { $sql .= $sql_element['prefix'] . implode($sql_element['glue'], $sql_element['pieces']); } } return db_rewrite_sql($sql); } /** * Construct an empty query for project_build_query(). * * Set the default elements that will be used to construct the SQL statement. */ function project_empty_query() { return array( 'fields' => array( 'prefix' => 'SELECT ', 'glue' => ', ', 'pieces' => array(), ), 'from' => array( 'prefix' => ' FROM ', 'glue' => NULL, 'pieces' => array(''), ), 'joins' => array( 'prefix' => '', 'glue' => ' ', 'pieces' => array(), ), 'wheres' => array( 'prefix' => ' WHERE ', 'glue' => ' AND ', 'pieces' => array(), ), 'group_bys' => array( 'prefix' => ' GROUP BY ', 'glue' => ', ', 'pieces' => array(), ), 'order_bys' => array( 'prefix' => ' ORDER BY ', 'glue' => ', ', 'pieces' => array(), ), 'parameters' => array( 'prefix' => NULL, 'glue' => NULL, 'pieces' => array(), ) ); } // TODO: this should probably be moved to cvs.module... function project_user($type, $edit, &$user, $category = NULL) { if ($type == 'view') { if ($projects = module_invoke('cvs', 'get_contributed_projects', $user->uid)) { return array(t('Projects') => $projects); } } } /** * Provide settings data related to methods for sorting projects. * */ function project_project_sort_methods($op, $method = NULL) { switch ($op) { // A listing of the available sorting methods. // The array values are the name of the module (in this case, 'project'). case 'methods': $methods = array(); $methods['name'] = 'project'; $methods['date'] = 'project'; if (project_use_taxonomy()) { $methods['category'] = 'project'; } return $methods; case 'sql_settings' : switch ($method) { case 'category': return array( 'order_bys' => array('n.sticky DESC', 'n.title ASC') ); case 'date': $date_sql = array( 'order_bys' => array('changed DESC', 'n.sticky DESC', 'n.title ASC'), 'group_bys' => array('n.nid'), ); if (module_exists('project_release')) { if (!variable_get('project_release_browse_versions', 0)) { $date_sql['fields'] = array('MAX(prn.file_date) AS changed'); $date_sql['joins'] = array('INNER JOIN {project_release_nodes} prn ON n.nid = prn.nid'); } } return $date_sql; case 'name': return array( 'order_bys' => array('n.title ASC', 'n.sticky DESC') ); default: return; } // Does this method reset the $term variable, and output term information? case 'set term': switch ($method) { case 'category': return TRUE; default: return; } // Does this method page results? case 'pager': switch ($method) { case 'category': case 'date': return TRUE; case 'name': return FALSE; } // Does this method group results by date? case 'group by date': switch ($method) { case 'category': return FALSE; case 'date': return TRUE; case 'name': return FALSE; default: return; } } return; } /** * Helper function for grouping nodes by date. */ function _project_date($timestamp) { static $last; $date = format_date($timestamp, 'custom', 'F j, Y'); if ($date != $last) { $last = $date; return $date; } } /** * Returns an array of all projects on the system for use with select * form elements. The keys are the project nid, and the values are * the project names. If the project taxonomy is in use, the array * will be sorted into the project categories with appropriate headers * for each term. * * @param $project_urls * Reference to be filled with an array of project uri => id mappings. This * array is used by the project_issue search form code. * @param $issues * If TRUE, only projects with issue tracking enabled are returned. * For this option to do much good, the project_issue.module should be * enabled. * @param $key_prefix * Prefix to prepend to all keys in the returned array. */ function project_projects_select_options(&$project_urls, $issues = TRUE, $key_prefix = NULL) { $projects = array(); if ($issues && module_exists('project_issue')) { $ISSUE_JOIN ='INNER JOIN {project_issue_projects} pip ON n.nid = pip.nid'; $ISSUE_WHERE = 'AND pip.issues = 1'; } if (project_use_taxonomy()) { $vid = _project_get_vid(); $result = db_query(db_rewrite_sql("SELECT n.nid, n.title, d.name, p.uri FROM {project_projects} p INNER JOIN {node} n ON n.nid = p.nid $ISSUE_JOIN LEFT JOIN {term_node} t ON t.nid = n.nid INNER JOIN {term_data} d ON t.tid = d.tid INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE n.status = 1 $ISSUE_WHERE AND d.vid = %d AND h.parent = 0 GROUP BY n.nid, n.title, d.name, p.uri, d.weight ORDER BY d.weight, n.title"), $vid); while ($project = db_fetch_object($result)) { if (isset($project->name)) { if (!isset($projects[$project->name])) { $projects[$project->name] = array(); } $projects[$project->name][$key_prefix . $project->nid] = $project->title; } else { $projects[$key_prefix . $project->nid] = $project->title; } if (is_array($project_urls)) { $project_urls[$project->uri] = $project->nid; } } } else { $result = db_query(db_rewrite_sql("SELECT n.nid, p.uri, n.title FROM {project_projects} p INNER JOIN {node} n ON n.nid = p.nid $ISSUE_JOIN WHERE n.status = 1 $ISSUE_WHERE ORDER BY n.title")); while ($project = db_fetch_object($result)) { $projects[$key_prefix . $project->nid] = $project->title; if (is_array($project_urls)) { $project_urls[$project->uri] = $project->nid; } } } return $projects; } function project_quick_navigate_form() { $uris = NULL; $projects = array_merge(array(0 => t('- Select a project -')), project_projects_select_options($uris, FALSE, 'node/')); $form = array(); $form['project_goto'] = array( '#type' => 'select', '#default_value' => '0', '#options' => $projects, '#attributes' => array('title' => t('Select a project to view')), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Go') ); return $form; } function project_quick_navigate_form_validate($form_id, $form_values) { if (empty($form_values['project_goto'])) { form_set_error('project_goto', t('You must select a project to navigate to.')); } } function project_quick_navigate_form_submit($form_id, $form_values) { drupal_goto($form_values['project_goto']); } // Themables function theme_project_term_list($terms, $path) { $depth = 0; $output = "\n\n"; return $output; } /** * Theme a compact project view/summary. * * $project has the following fields: * - title: Title * - nid: Node id * - body: Filtered description * - term: String with the selected term name * - version: String with the version * - links: Array of links */ function theme_project_summary($project) { $output = '
'; $output .= '

'. l($project->title, "node/$project->nid") .'

'; if (!empty($project->changed)) { $output .= '

' . t('Last changed: !interval ago', array('!interval' => format_interval(time() - $project->changed, 2))) . '

'; } $output .= $project->body; $output .= theme('links', $project->links); if (!empty($project->download_table)) { $output .= $project->download_table; } if (!empty($project->terms)) { $output .= theme('links', $project->terms); } $output .= '
'; return $output; } /** * Theme a project type item. * */ function theme_project_type($term) { $link = l($term->name, check_url('project/'. $term->name)); $output = "
$link
\n"; if ($term->description) { $output .= '

' . filter_xss($term->description) . '

'; } return $output; } /** * Returns a string of valid project names formatted to be suitable * for use with JS autocomplete fields. The user enters a * comma-separated list of project names. We only autocomplete the * last one. This code is stolen heavily from taxonomy_autocomplete(). */ function project_autocomplete($string) { // This regexp allows the following types of user input: // this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x'; preg_match_all($regexp, $string, $matches); $array = $matches[1]; // Fetch last project $last_string = trim(array_pop($array)); if ($last_string != '') { $result = db_query_range("SELECT n.title FROM {node} n INNER JOIN {project_projects} p ON n.nid = p.nid WHERE LOWER(n.title) LIKE LOWER('%%%s%%')", $last_string, 0, 10); $prefix = count($array) ? implode(', ', $array) .', ' : ''; $matches = array(); while ($project = db_fetch_object($result)) { $t = $project->title; $matches[$prefix . $t] = check_plain($project->title); } print drupal_to_js($matches); exit(); } } /** * Returns whether or not the project module should use * taxonomy-specific functionality. */ function project_use_taxonomy() { return module_exists('taxonomy') && taxonomy_get_tree(_project_get_vid()); } /** * Returns whether or not the project uses * version control or not */ function project_use_cvs($project) { if (module_exists('cvs')) { $project = is_numeric($project) ? node_load($project) : $project; return isset($project->cvs_repository) && ($project->cvs_repository != 0); } } /** * Determines if the current site supports caching of project-related pages. * * The pages can be cached if: * 1. Anonymous users have the 'access projects' permission. * 2. No node access modules are installed. * * @return TRUE if the output can be cached, FALSE otherwise. */ function project_can_cache() { $grants = module_implements('node_grants'); if (!empty($grants)) { return FALSE; } $allowed_roles = user_roles(FALSE, 'access projects'); if (!isset($allowed_roles[DRUPAL_ANONYMOUS_RID])) { return FALSE; } return TRUE; } /** * Display an RSS feed icon for use on project nodes. */ function theme_project_feed_icon($url, $title) { if ($icon = theme('image', 'misc/feed.png', $title, $title)) { return ''. $icon .''; } }