'; /* Project taxonomy */ if (project_use_taxonomy()) { drupal_add_js(drupal_get_path('module', 'project') .'/project.js'); $tree = taxonomy_get_tree(_project_get_vid()); $top_level = array(); $options = array(); foreach ($tree as $i => $term) { if ($term->parents[0] == 0) { $last_top = $term->tid; $top_level[$term->tid] = check_plain($term->name); } else { $options[$last_top][$term->tid] = $term->name; } } // See if there are any project specific taxonomy terms already // saved in this node (i.e. we're editing an existing project) and // if so, extract the right default values for our custom form // elements... if ($node->taxonomy) { foreach ($node->taxonomy as $tid => $obj) { if ($top_level[$tid]) { $current_top = $tid; } else { $current_options[$tid] = $tid; } } } $form['project_taxonomy'] = array( '#type' => 'fieldset', '#weight' => '-30', '#title' => t('Project categories'), '#collapsible' => TRUE, ); $form['project_taxonomy']['project_type'] = array( '#title' => t('Project type'), '#type' => 'radios', '#prefix' => '
', '#suffix' => '
', '#options' => $top_level, '#default_value' => $current_top, '#required' => TRUE, ); $select_size = max(5, 2*count($top_level)); foreach ($options as $tid => $values) { $form['project_taxonomy']["tid_$tid"] = array( '#title' => t('!type categories', array('!type' => $top_level[$tid])), '#type' => 'select', '#multiple' => TRUE, '#options' => $values, '#default_value' => $current_options, '#attributes' => array('size' => min($select_size, count($values))), '#prefix' => '
', '#suffix' => '
', ); } } /* Project properties */ $form['project'] = array( '#type' => 'fieldset', '#title' => t('Project information'), '#collapsible' => TRUE, ); $form['project']['title'] = array( '#type' => 'textfield', '#title' => t('Full project name'), '#default_value' => isset($node->title) ? $node->title : NULL, '#maxlength' => 128, '#required' => TRUE, ); $form['project']['body'] = array( '#type' => 'textarea', '#title' => t('Full description'), '#default_value' => isset($node->body) ? $node->body : NULL, '#cols' => 40, '#rows' => 10, '#required' => TRUE, ); $form['project']['format'] = filter_form($node->format); $form['project']['uri'] = array( '#type' => 'textfield', '#title' => t('Short project name'), '#default_value' => isset($node->uri) ? $node->uri : NULL, '#size' => 40, '#maxlength' => 50, '#description' => t('This will be used to generate a /project/<shortname>/ URL for your project.'), '#required' => TRUE, ); $form['project']['homepage'] = array( '#type' => 'textfield', '#title' => t('Homepage'), '#default_value' => isset($node->homepage) ? $node->homepage : NULL, '#size' => 40, '#maxlength' => 255, '#description' => t('Link to project homepage.'), ); $form['project']['documentation'] = array( '#type' => 'textfield', '#title' => t('Documentation'), '#default_value' => isset($node->documentation) ? $node->documentation : NULL, '#size' => 40, '#maxlength' => 255, '#description' => t('Link to project documentation.'), ); $form['project']['license'] = array( '#type' => 'textfield', '#title' => t('License'), '#default_value' => isset($node->license) ? $node->license : NULL, '#size' => 40, '#maxlength' => 255, '#description' => t('Link to project license.'), ); $form['project']['screenshots'] = array( '#type' => 'textfield', '#title' => t('Screenshots'), '#default_value' => isset($node->screenshots) ? $node->screenshots : NULL, '#size' => 40, '#maxlength' => 255, '#description' => t('Link to project screenshots.'), ); $form['project']['changelog'] = array( '#type' => 'textfield', '#title' => t('Changelog'), '#default_value' => isset($node->changelog) ? $node->changelog : NULL, '#size' => 40, '#maxlength' => 255, '#description' => t('Link to changelog.'), ); $form['project']['cvs'] = array( '#type' => 'textfield', '#title' => t('CVS tree'), '#default_value' => isset($node->cvs) ? $node->cvs : NULL, '#size' => 40, '#maxlength' => 255, '#description' => t('Link to webcvs/viewcvs.'), ); $form['project']['demo'] = array( '#type' => 'textfield', '#title' => t('Demo site'), '#default_value' => isset($node->demo) ? $node->demo : NULL, '#size' => 40, '#maxlength' => 255, '#description' => t('Link to a live demo.'), ); $form['#suffix'] = ''; return $form; } function project_project_validate(&$node) { // Bail if user hasn't done a preview yet. if (!isset($node->title)) { return $node; } // Make sure title isn't already in use if (db_num_rows(db_query("SELECT nid FROM {node} WHERE type = '%s' AND status = 1 AND title = '%s' AND nid <> %d", $node->type, $node->title, $node->nid))) { form_set_error('title', t('This project name is already in use.')); } // Validate uri. if (empty($node->uri)) { form_set_error('uri', t('A short project name is required.')); } else { // Make sure uri only includes valid characters if (!preg_match('/^[a-zA-Z0-9_-]+$/', $node->uri)) { form_set_error('uri', t('Please only use alphanumerical characters for the project name.')); } // Make sure uri isn't already in use, or reserved. Includes all X from // project/issues/X paths used in project_issues module $reserved_names = array('user', 'issues', 'releases', 'rss', 'subscribe-mail', 'search', 'add', 'update_project', 'statistics', 'comments', 'autocomplete', 'cvs', 'developers'); if (project_use_taxonomy()) { $terms = taxonomy_get_tree(_project_get_vid()); foreach ($terms as $i => $term) { if ($term->depth == 0) { $reserved_names[] = strtolower($term->name); } } } if (in_array(strtolower($node->uri), $reserved_names) || db_num_rows(db_query("SELECT nid FROM {project_projects} WHERE uri = '%s' AND nid <> %d", $node->uri, $node->nid))) { form_set_error('uri', t('This project name is already in use.')); } } // We need a description. if (empty($node->body)) { form_set_error('body', t('You must add a project description.')); } // Make sure all URL fields actually contain URLs. $fields = array( 'homepage' => t('Homepage'), 'changelog' => t('Changelog'), 'cvs' => t('CVS tree'), 'demo' => t('Demo site'), ); foreach ($fields as $uri => $name) { if ($node->$uri && !preg_match('/^(http|https|ftp):\/\//i', $node->$uri)) { form_set_error($uri, t('!field is not a valid URL.', array('!field' => $name))); } } // Validate the project-specific sub-categories, if any... if (project_use_taxonomy() && $node->project_type) { $tree = taxonomy_get_tree(_project_get_vid()); $top_level = array(); foreach ($tree as $i => $term) { if ($term->parents[0] == 0) { $top_level[$term->tid] = $term->name; } } foreach ($top_level as $tid => $name) { if ($node->project_type != $tid) { $tid_field = 'tid_' . $tid; if (!empty($node->$tid_field)) { form_set_error($tid, t('Project type %project_type was selected, you can not use values from %invalid_type categories', array('%project_type' => $top_level[$node->project_type], '%invalid_type' => $top_level[$tid]))); } } } } } function project_project_set_breadcrumb($node = NULL, $extra = NULL) { global $_menu; $breadcrumb = array(); $breadcrumb[] = l(t('Home'), NULL); // Find out if the site has created a menu name for /project and use that $pid = $_menu['path index']['project']; $name = $_menu['items'][$pid]['title']; $breadcrumb[] = l($name, 'project', array('title' => t('Browse projects'))); if (!empty($node) && project_use_taxonomy()) { $result = db_query(db_rewrite_sql('SELECT t.tid, t.* FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid INNER JOIN {term_node} r ON t.tid = r.tid WHERE h.parent = 0 AND t.vid = %d AND r.nid = %d', 't', 'tid'), _project_get_vid(), $node->nid); $term = db_fetch_object($result); $breadcrumb[] = l($term->name, 'project/'. $term->name); } if (is_array($extra)) { $breadcrumb = array_merge($breadcrumb, $extra); } elseif ($extra && !empty($node)) { $breadcrumb[] = l($node->title, 'node/'. $node->nid); } drupal_set_breadcrumb($breadcrumb); } function project_project_view($node, $teaser = false, $page = false) { $node = node_prepare($node, $teaser); if ($page) { // Breadcrumb navigation project_project_set_breadcrumb($node); // If theme_project_release_project_download_table is implemented, format // the download table. If this function is not implemented (eg. if the // project_release module is not enabled), there will not be an error // but of course there will be no release table. $project_table_output = theme('project_release_project_download_table', $node); if (!empty($project_table_output)) { $node->content['download_table'] = array( '#value' => $project_table_output, '#weight' => 1, ); } // Build a nested array of sections of links to display on project_project node pages. $all_links = array(); // Resources section $all_links['resources'] = array( 'name' => t('Resources'), 'weight' => 4, ); foreach (array('homepage' => t('Home page'), 'documentation' => t('Read documentation'), 'license' => t('Read license'), 'changelog' => t('Read complete log of changes'), 'demo' => t('Try out a demonstration'), 'screenshots' => t('Look at screenshots')) as $uri => $name) { if (!empty($node->$uri)) { $all_links['resources']['links'][$uri] = l($name, $node->$uri); } } // Flags that indicate what kind of access to project issues to allow. $has_issues = module_exists('project_issue') && !empty($node->issues); $view_issues = $has_issues && (user_access('access project issues') || user_access('access own project issues') || user_access('administer projects')); $make_issues = $has_issues && node_access('create', 'project_issue'); // Support section. $all_links['support'] = array( 'name' => t('Support'), 'weight' => 6, ); $links = array(); if ($view_issues) { $links['all_support'] = l(t('View all support requests'), 'project/issues/'. $node->uri, null, 'categories=support&states=all', null); $links['pending_support'] = l(t('View pending support requests'), 'project/issues/'. $node->uri, null, 'categories=support', null); $links['pending_bugs'] = l(t('View pending bug reports'), 'project/issues/'. $node->uri, null, 'categories=bug', null); $links['pending_features'] = l(t('View pending feature requests'), 'project/issues/'. $node->uri, null, 'categories=feature', null); } if ($make_issues) { $links['request_support'] = l(t('Request support'), 'node/add/project_issue/'. $node->uri .'/support'); $links['report_bug'] = l(t('Report new bug'), 'node/add/project_issue/'. $node->uri .'/bug'); $links['request_feature'] = l(t('Request new feature'), 'node/add/project_issue/'. $node->uri .'/feature'); } else { $links['create_forbidden'] = theme('project_issue_create_forbidden', $node->uri); } $all_links['support']['links'] = $links; // Developer section $all_links['development'] = array( 'name' => t('Development'), 'weight' => 8, ); $links = array(); if ($view_issues) { $links['pending_patches'] = l(t('View pending patches'), 'project/issues/'. $node->uri, null, 'states=8,13,14', null); $links['available_tasks'] = l(t('View available tasks'), 'project/issues/'. $node->uri, null, 'categories=task', null); $links['pending_issues'] = l(t('View all pending issues'), 'project/issues/'. $node->uri); } if ($node->cvs) { $links['browse_repository'] = l(t('Browse the CVS repository'), $node->cvs); } if (project_use_cvs($node)) { $links['view_cvs_messages'] = l(t('View CVS messages'), 'project/cvs/'. $node->nid); $links['developers'] = l(t('Developers'), 'project/developers/'. $node->nid); } $all_links['development']['links'] = $links; // Allow other modules to add sections of links and/or links to the sections defined above. project_page_link_alter($node, $all_links); // Format links in $all_links for display in the project_project node. foreach($all_links as $section => $values) { // Only add the section if there are links for the section. if (!empty($values['links'])) { $node->content[$section] = array( '#value' => theme('item_list', $values['links'], $values['name']), '#weight' => !empty($values['weight']) ? $values['weight'] : 0, ); } } } return $node; } function project_project_load($node) { $project = db_fetch_object(db_query('SELECT * FROM {project_projects} WHERE nid = %d', $node->nid)); return $project; } /** * hook_nodeapi() implementation specific for project nodes. * @see project_nodeapi(). */ function project_project_nodeapi(&$node, $op, $arg) { switch ($op) { case 'insert': _project_save_taxonomy($node); if (module_exists('path')) { path_set_alias("node/$node->nid", "project/$node->uri"); } break; case 'update': _project_save_taxonomy($node); if (module_exists('path')) { path_set_alias("node/$node->nid"); // Clear existing alias. path_set_alias("node/$node->nid", "project/$node->uri"); } break; } } function project_project_insert($node) { db_query("INSERT INTO {project_projects} (nid, uri, homepage, changelog, cvs, demo, release_directory, screenshots, documentation, license) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $node->nid, $node->uri, $node->homepage, $node->changelog, $node->cvs, $node->demo, $node->release_directory, $node->screenshots, $node->documentation, $node->license); // project_release_scan_directory($node->uri); } function project_project_update($node) { db_query("UPDATE {project_projects} SET uri = '%s', homepage = '%s', changelog = '%s', cvs = '%s', demo = '%s', release_directory = '%s', screenshots = '%s', documentation = '%s', license = '%s' WHERE nid = %d", $node->uri, $node->homepage, $node->changelog, $node->cvs, $node->demo, $node->release_directory, $node->screenshots, $node->documentation, $node->license, $node->nid); // project_release_scan_directory($node->uri); } function project_project_delete($node) { db_query('DELETE FROM {project_projects} WHERE nid = %d', $node->nid); } function project_project_access($op, $node) { global $user; switch ($op) { case 'view': // Since this function is shared for project_release nodes, we have to // be careful what node we pass to project_check_admin_access(). if ($node->type == 'project_release') { $node = node_load($node->pid); } if (project_check_admin_access($node)) { return TRUE; } if (!user_access('access projects')) { return FALSE; } break; case 'create': if ($user->uid && user_access('maintain projects')) { // Since this CVS access checking is non-standard, we need to // special-case uid 1 to always allow everything. if ($user->uid != 1 && module_exists('cvs') && variable_get('cvs_restrict_project_creation', 1)) { return db_result(db_query("SELECT uid FROM {cvs_accounts} WHERE uid = %d AND status = %d", $user->uid, CVS_APPROVED)) ? TRUE : FALSE; } else { return TRUE; } } break; case 'update': if (project_check_admin_access($node)) { return TRUE; } break; case 'delete': if (project_check_admin_access($node, FALSE)) { return TRUE; } break; } } function project_project_retrieve($key = 0) { if ($key) { if (is_numeric($key)) { return node_load(array('nid' => $key, 'type' => 'project_project')); } else { $nid = db_result(db_query("SELECT nid FROM {project_projects} WHERE uri = '%s'", $key), 0); if (!$nid) { return new StdClass(); } else { return node_load(array('nid' => $nid, 'type' => 'project_project')); } } } return NULL; } function project_developers($nid = 0) { if ($project = node_load($nid)) { if (node_access('view', $project)) { $output = module_invoke('cvs', 'get_project_contributors', $nid); drupal_set_title(t('Developers for %name', array('%name' => check_plain($project->title)))); project_project_set_breadcrumb($project, TRUE); return $output; } else { drupal_access_denied(); } } else { drupal_not_found(); } } function project_cvs($nid = 0) { if ($project = node_load($nid)) { if (node_access('view', $project)) { $_REQUEST['nid'] = $nid; $output = module_invoke('cvs', 'show_messages'); drupal_set_title(t('CVS messages for %name', array('%name' => check_plain($project->title)))); project_project_set_breadcrumb($project, TRUE); return $output; } else { drupal_access_denied(); } } else { drupal_not_found(); } } function _project_save_taxonomy(&$node) { if (project_use_taxonomy() && $node->project_type) { // First, clear out all terms from the project-specific taxonomy // in this node. We'll re-add the right ones based on what's saved. // This way, we're sure to clear out things that have been changed. $vid = _project_get_vid(); $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid); $items = array(); while ($item = db_fetch_object($result)) { $items[] = "tid = $item->tid"; } if ($items) { $sql = 'DELETE FROM {term_node} WHERE nid = %d AND ('. implode(' OR ', $items) . ')'; db_query($sql, $node->nid); } $tid = $node->project_type; _project_db_save_taxonomy($node->nid, $tid); $tid_field = 'tid_' . $tid; if (isset($node->$tid_field)) { foreach ($node->$tid_field as $tid) { _project_db_save_taxonomy($node->nid, $tid); } } } } function _project_db_save_taxonomy($nid, $tid) { db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $tid); } /** * Allow modules to alter the project page links. * * @param $node * The project_project node object. This can be useful for modules that * implement this hook to determine whether a user viewing * the node should have access to certain links. See the * implementation of this hook in project_release.module for an * example. * NOTE: $node is passed by value and not by reference, so modules * implementing this hook cannot actually modify the node object. * @param $links * An array of links. * @param $section * The link section of the project page. */ function project_page_link_alter($node, &$all_links) { foreach (module_implements('project_page_link_alter') as $module) { $function = $module .'_project_page_link_alter'; $function($node, $all_links); } }