Parent Directory
|
Revision Log
|
Revision Graph
#98278 by dww: Follow-up fix in project_project_access() when checking view access on a project_release node -- we need to inspect $node->project_release['pid'] not $node->project['pid'].
| 1 | <?php |
| 2 | // $Id: project.module,v 1.354 2009/08/10 22:50:39 dww Exp $ |
| 3 | |
| 4 | /** |
| 5 | * Implementation of hook_init(). |
| 6 | */ |
| 7 | function project_init() { |
| 8 | $path = drupal_get_path('module', 'project'); |
| 9 | drupal_add_css($path .'/project.css'); |
| 10 | if (file_exists("$path/project.inc")) { |
| 11 | require_once "$path/project.inc"; |
| 12 | } |
| 13 | } |
| 14 | |
| 15 | function project_help($path, $arg) { |
| 16 | switch ($path) { |
| 17 | case 'admin/project/project-settings': |
| 18 | if (project_use_taxonomy()) { |
| 19 | return _project_taxonomy_help(); |
| 20 | } |
| 21 | break; |
| 22 | case 'admin/content/taxonomy/%': |
| 23 | $vid = _project_get_vid(); |
| 24 | if ($arg[3] == $vid) { |
| 25 | return _project_taxonomy_help($vid, FALSE); |
| 26 | } |
| 27 | break; |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | /** |
| 32 | * Prints out a help message about how to configure the project vocabulary. |
| 33 | * |
| 34 | * @param $vid |
| 35 | * Vocabulary ID of the project taxonomy. |
| 36 | * @param $vocab_link |
| 37 | * Boolean that controls if a link to the vocabulary admin page is added. |
| 38 | */ |
| 39 | function _project_taxonomy_help($vid = 0, $vocab_link = TRUE) { |
| 40 | if (!$vid) { |
| 41 | $vid = _project_get_vid(); |
| 42 | } |
| 43 | $vocabulary = taxonomy_vocabulary_load($vid); |
| 44 | $text = '<p>'. 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)) .'</p>'; |
| 45 | $text .= '<p>'. 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".') .'</p>'; |
| 46 | $text .= '<p>'. 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".') .'</p>'; |
| 47 | if ($vocab_link) { |
| 48 | $text .= '<p>'. t('Use the <a href="@taxonomy-admin">vocabulary administration page</a> to view and add terms.', array('@taxonomy-admin' => url('admin/content/taxonomy/'. $vid))) .'</p>'; |
| 49 | } |
| 50 | return $text; |
| 51 | } |
| 52 | |
| 53 | /** |
| 54 | * Implementation of hook_block(). |
| 55 | */ |
| 56 | function project_block($op = 'list', $delta = 0, $edit = array()) { |
| 57 | if ($op == 'list') { |
| 58 | // Note: We can get by with using BLOCK_CACHE_PER_ROLE below because |
| 59 | // block caching is disabled when node access control modules are in use. |
| 60 | $blocks['project_navigation_select'] = array( |
| 61 | 'info' => t('Project navigation (drop-down select)'), |
| 62 | 'cache' => BLOCK_CACHE_PER_ROLE, |
| 63 | ); |
| 64 | $blocks['project_navigation_text'] = array( |
| 65 | 'info' => t('Project navigation (text field)'), |
| 66 | 'cache' => BLOCK_CACHE_PER_ROLE, |
| 67 | ); |
| 68 | if (module_exists('search')) { |
| 69 | $blocks['project_search'] = array( |
| 70 | 'info' => t('Project search'), |
| 71 | 'cache' => BLOCK_CACHE_PER_ROLE, |
| 72 | ); |
| 73 | } |
| 74 | |
| 75 | module_load_include('inc', 'project', 'project'); |
| 76 | foreach (project_get_project_link_info() as $key => $links) { |
| 77 | if (isset($links['type']) && $links['type'] == 'block') { |
| 78 | $blocks[$key] = array( |
| 79 | 'info' => t('Project: @section', array('@section' => $links['name'])), |
| 80 | 'cache' => BLOCK_CACHE_PER_ROLE | BLOCK_CACHE_PER_PAGE, |
| 81 | ); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | return $blocks; |
| 86 | } |
| 87 | else if ($op == 'view') { |
| 88 | switch ($delta) { |
| 89 | case 'project_navigation_select': |
| 90 | $block = array( |
| 91 | 'subject' => t('Project navigation'), |
| 92 | 'content' => drupal_get_form('project_quick_navigate_form'), |
| 93 | ); |
| 94 | break; |
| 95 | |
| 96 | case 'project_navigation_text': |
| 97 | $block = array( |
| 98 | 'subject' => t('Project navigation'), |
| 99 | 'content' => drupal_get_form('project_quick_navigate_title_form'), |
| 100 | ); |
| 101 | break; |
| 102 | |
| 103 | case 'project_search': |
| 104 | if (user_access('search content')) { |
| 105 | $block = array( |
| 106 | 'subject' => t('Search projects'), |
| 107 | 'content' => drupal_get_form('project_search_block_form', 'project_project'), |
| 108 | ); |
| 109 | } |
| 110 | break; |
| 111 | |
| 112 | } |
| 113 | if (empty($block) && ($node = project_get_project_from_menu()) && node_access('view', $node)) { |
| 114 | module_load_include('inc', 'project', 'project'); |
| 115 | foreach (project_get_project_link_info($node) as $key => $links) { |
| 116 | if ($delta == $key && isset($links['type']) && $links['type'] == 'block' && !empty($links['links'])) { |
| 117 | $block = array( |
| 118 | 'subject' => $links['name'], |
| 119 | 'content' => theme('item_list', $links['links']), |
| 120 | ); |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | return $block; |
| 125 | } |
| 126 | elseif ($op == 'configure' && $delta == 1) { |
| 127 | $form = array(); |
| 128 | $form['help_text'] = array( |
| 129 | '#type' => 'textfield', |
| 130 | '#title' => t('Help text'), |
| 131 | '#description' => t('Enter optional help text to display in the block.'), |
| 132 | '#default_value' => variable_get('project_search_block_help_text', ''), |
| 133 | ); |
| 134 | return $form; |
| 135 | } |
| 136 | elseif ($op == 'save' && $delta == 1) { |
| 137 | variable_set('project_search_block_help_text', $edit['help_text']); |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | function project_search_block_form($form_state, $node_type) { |
| 142 | $form = search_box($form_state, 'project_search_block_form'); |
| 143 | $form['node_type'] = array( |
| 144 | '#type' => 'value', |
| 145 | '#value' => $node_type, |
| 146 | ); |
| 147 | $form['#base'] = 'project_search_block_form'; |
| 148 | $help_text = variable_get('project_search_block_help_text', ''); |
| 149 | if (!empty($help_text)) { |
| 150 | $element = 'project_search_block_form'; |
| 151 | $form[$element]['#description'] = check_plain($help_text); |
| 152 | } |
| 153 | $form['#submit'][] = 'project_search_block_form_submit'; |
| 154 | return $form; |
| 155 | } |
| 156 | |
| 157 | function project_search_block_form_submit($form, &$form_state) { |
| 158 | $form_state['redirect'] = 'search/node/type:'. $form_state['values']['node_type'] .' '. trim($form_state['values'][$form_state['values']['form_id']]); |
| 159 | } |
| 160 | |
| 161 | function project_node_info() { |
| 162 | return array( |
| 163 | 'project_project' => array( |
| 164 | 'name' => t('Project'), |
| 165 | 'module' => 'project_project', |
| 166 | '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.'), |
| 167 | ), |
| 168 | ); |
| 169 | } |
| 170 | |
| 171 | function project_perm() { |
| 172 | $perms = array( |
| 173 | 'administer projects', |
| 174 | 'maintain projects', |
| 175 | 'access projects', |
| 176 | 'access own projects', |
| 177 | 'delete any projects', |
| 178 | 'delete own projects', |
| 179 | 'browse project listings', |
| 180 | ); |
| 181 | return $perms; |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * Implementation of hook_db_rewrite_sql(). |
| 186 | * |
| 187 | * Both project (and, if enabled, project_issue) provide some permissions that |
| 188 | * restrict access to viewing projects (and issues). For these permissions to |
| 189 | * be globally honored by the system, db_rewrite_sql() has to check what |
| 190 | * permissions the current user has and restrict query results accordingly. |
| 191 | * |
| 192 | * If a user has 'access projects' and 'access project issues', they have full |
| 193 | * access, so there's nothing to re-write. If they have 'access own projects' |
| 194 | * and/or 'access own project issues', the resulting query should JOIN on |
| 195 | * {node} and ensure that the node type is not project_project or |
| 196 | * project_issue, or that the owner of the node is the current user. If they |
| 197 | * do not have any access to either, then we can just restrict the query based |
| 198 | * on the {node}.type column. |
| 199 | * |
| 200 | * @see project_perm() |
| 201 | * @see project_issue_perm() |
| 202 | * @see project_find_alias() |
| 203 | */ |
| 204 | function project_db_rewrite_sql($query, $primary_table, $primary_field) { |
| 205 | if ($primary_field == 'nid') { |
| 206 | $return = array(); |
| 207 | $access_projects = user_access('access projects'); |
| 208 | $admin_projects = user_access('administer projects'); |
| 209 | if (module_exists('project_issue')) { |
| 210 | $access_issues = user_access('access project issues'); |
| 211 | $access_own_issues = user_access('access own project issues'); |
| 212 | } |
| 213 | else { |
| 214 | $access_issues = TRUE; |
| 215 | $access_own_issues = TRUE; |
| 216 | } |
| 217 | if ($admin_projects || ($access_projects && $access_issues)) { |
| 218 | // User has full access, nothing to re-write. |
| 219 | return; |
| 220 | } |
| 221 | else { |
| 222 | // We have to make sure {node} is in the query and know the alias. |
| 223 | if ($primary_table == 'n' || $primary_table == 'node') { |
| 224 | // Great, it's the primary table and we already know the alias. |
| 225 | $alias = $primary_table; |
| 226 | } |
| 227 | else { |
| 228 | // Look for {node} in the query. |
| 229 | if (!($alias = project_find_alias($query, 'node'))) { |
| 230 | // Not already in the query, JOIN it. |
| 231 | $return['join'] = "INNER JOIN {node} pn ON pn.nid = ". $primary_table .'.nid'; |
| 232 | $alias = 'pn'; |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | // At this point, we know we have to restrict something, and we know what |
| 237 | // the {node} table's alias is in the query. |
| 238 | $where = array(); |
| 239 | |
| 240 | global $user; |
| 241 | $uid = $user->uid; |
| 242 | |
| 243 | // Some node types will be restriced by our query, but we want to allow |
| 244 | // every other node type. We keep track of the types we're handling, and |
| 245 | // at the end we'll add a clause to ignore/allow all other types. |
| 246 | $restricted_types = array(); |
| 247 | |
| 248 | if (!$access_projects) { |
| 249 | $restricted_types[] = "'project_project'"; |
| 250 | if (user_access('access own projects')) { |
| 251 | $where[] = "($alias.type = 'project_project' AND $alias.uid = $uid)"; |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | if (module_exists('project_issue') && !$access_issues) { |
| 256 | $restricted_types[] = "'project_issue'"; |
| 257 | if ($access_own_issues) { |
| 258 | $where[] = "($alias.type = 'project_issue' AND $alias.uid = $uid)"; |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | // If the type is not one of the restricted project* ones, allow access. |
| 263 | $where[] = "($alias.type NOT IN (". implode(', ', $restricted_types) ."))"; |
| 264 | |
| 265 | // Now that we have all our WHERE clauses, we just OR them all together. |
| 266 | $return['where'] = implode(' OR ', $where); |
| 267 | return $return; |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | /** |
| 272 | * Find the table alias for a table in a query. |
| 273 | * |
| 274 | * @param $query |
| 275 | * The query to examine for the alias. |
| 276 | * @param |
| 277 | * $table The table to examine for the alias. |
| 278 | * @return |
| 279 | * The alias if it exists, or the name of the table if it's present but not |
| 280 | * aliased, or FALSE if the table is not present. |
| 281 | * |
| 282 | * @see project_db_rewrite_sql() |
| 283 | */ |
| 284 | function project_find_alias($query, $table) { |
| 285 | // See if {$table} is already in the query. |
| 286 | $match = array(); |
| 287 | // This regexp handles many cases: |
| 288 | // - {$table} can be either in FROM or in a JOIN clause |
| 289 | // - Query might end immediately after {$table} |
| 290 | // - Might not have a table alias for {$table} |
| 291 | $pattern = "@.*(FROM|JOIN)\s+\{$table\}\s*(\S+)?@"; |
| 292 | if (preg_match($pattern, $query, $match)) { |
| 293 | $keywords = '@(ON|INNER|LEFT|RIGHT|WHERE|ORDER|GROUP|HAVING|LIMIT)@'; |
| 294 | if (!isset($match[2]) || preg_match($keywords, $match[2])) { |
| 295 | // No alias found, just use $table. |
| 296 | $alias = $table; |
| 297 | } |
| 298 | else { |
| 299 | // Alias found. |
| 300 | $alias = $match[2]; |
| 301 | } |
| 302 | } |
| 303 | else { |
| 304 | // Table not in query. |
| 305 | $alias = FALSE; |
| 306 | } |
| 307 | |
| 308 | return $alias; |
| 309 | } |
| 310 | |
| 311 | /** |
| 312 | * Callback for the main settings page. |
| 313 | * |
| 314 | * @TODO: This function is probably one we can delete, since there are no settings |
| 315 | * to be set anymore. |
| 316 | */ |
| 317 | function project_settings_form() { |
| 318 | $form = array(); |
| 319 | |
| 320 | if (module_exists('path')) { |
| 321 | $form['project_enable_alias'] = array( |
| 322 | '#type' => 'checkbox', |
| 323 | '#title' => t('Enable auto-generated project aliases'), |
| 324 | '#default_value' => variable_get('project_enable_alias', TRUE), |
| 325 | '#description' => t('If checked, project module will automatically generate path aliases (eg. /project/<shortname>/) for each project. Uncheck if you want to use another module, such as pathauto, to generate aliases.') |
| 326 | ); |
| 327 | } |
| 328 | else { |
| 329 | $form['project_enable_alias'] = array( |
| 330 | '#type' => 'markup', |
| 331 | '#value' => t('If you enable the Path module from Drupal core, you can control if the Project module will automatically generate path aliases (eg. /project/<shortname>/) for each project.') |
| 332 | ); |
| 333 | variable_set('project_enable_alias', FALSE); |
| 334 | } |
| 335 | |
| 336 | return system_settings_form($form); |
| 337 | } |
| 338 | |
| 339 | /** |
| 340 | * Returns the vocabulary id for projects. |
| 341 | */ |
| 342 | function _project_get_vid() { |
| 343 | $vid = variable_get('project_vocabulary', ''); |
| 344 | if (empty($vid)) { |
| 345 | // Check to see if a project module vocabulary exists. |
| 346 | $vid = db_result(db_query("SELECT vid FROM {vocabulary} WHERE module='%s'", 'project')); |
| 347 | if (!$vid) { |
| 348 | $edit = array( |
| 349 | 'name' => t('Project types'), |
| 350 | 'multiple' => 1, |
| 351 | 'hierarchy' => 1, |
| 352 | 'relations' => 0, |
| 353 | 'module' => 'project', |
| 354 | 'nodes' => array('project_project' => 1), |
| 355 | ); |
| 356 | // If there is already a vocabulary assigned to 'project_project' nodes, use it. |
| 357 | $vocabularies = taxonomy_get_vocabularies('project_project'); |
| 358 | if (count($vocabularies)) { |
| 359 | $vocabulary = reset($vocabularies); |
| 360 | $edit['vid'] = $vocabulary->vid; |
| 361 | } |
| 362 | taxonomy_save_vocabulary($edit); |
| 363 | $vid = $edit['vid']; |
| 364 | } |
| 365 | variable_set('project_vocabulary', $vid); |
| 366 | } |
| 367 | |
| 368 | return $vid; |
| 369 | } |
| 370 | |
| 371 | /** |
| 372 | * Implementation of hook_term_path(). |
| 373 | * |
| 374 | * @TODO: Modify this function to use the new project settings where the |
| 375 | * user selects the views to use. |
| 376 | */ |
| 377 | function project_term_path($term) { |
| 378 | // Make sure we have the entire term object. |
| 379 | $term = taxonomy_get_term($term->tid); |
| 380 | |
| 381 | // The path must include the first-level term name for this term. |
| 382 | $tree = taxonomy_get_tree(_project_get_vid()); |
| 383 | $parents = taxonomy_get_parents_all($term->tid); |
| 384 | foreach($parents as $parent) { |
| 385 | $ancestors[] = $parent->tid; |
| 386 | } |
| 387 | foreach ($tree as $t) { |
| 388 | if (in_array($t->tid, $ancestors) && $t->depth == 0) { |
| 389 | $termname = $t->name; |
| 390 | if ($term->name == $termname) { |
| 391 | return 'project/' . drupal_strtolower($termname); |
| 392 | } |
| 393 | break; |
| 394 | } |
| 395 | } |
| 396 | // TODO: when the project browsing views work, if there's a direct URL to |
| 397 | // link to a project category view, we should add that here. |
| 398 | // Also, note that in the project_solr case, we'd like to link to, |
| 399 | // e.g. "project/modules?filter=tid:12" but it's apparently impossible to |
| 400 | // return url() for this since things get double encoded or something. |
| 401 | // So, for now, we punt and always link the subterms to the taxonomy page. |
| 402 | return 'taxonomy/term/'. $term->tid; |
| 403 | } |
| 404 | |
| 405 | /** |
| 406 | * Implementation of hook_taxonomy(). |
| 407 | */ |
| 408 | function project_taxonomy($op, $type, $array = NULL) { |
| 409 | if ($op == 'delete' && $type == 'vocabulary' && $array['vid'] == _project_get_vid()) { |
| 410 | variable_del('project_vocabulary'); |
| 411 | } |
| 412 | } |
| 413 | |
| 414 | /** |
| 415 | * Determine if the currently logged in user could have access to any project_project nodes. |
| 416 | */ |
| 417 | function project_project_access_any() { |
| 418 | return user_access('access projects') || user_access('access own projects') || user_access('administer projects'); |
| 419 | } |
| 420 | |
| 421 | /** |
| 422 | * Implement hook_menu(). |
| 423 | */ |
| 424 | function project_menu() { |
| 425 | $items = array(); |
| 426 | |
| 427 | // @TODO: The project type and browsing views are currently set to require |
| 428 | // 'access projects' permission. However, we really want them to require at |
| 429 | // least one of 'access projects', 'access own projects', or 'administer |
| 430 | // projects'. At this point I'm not sure what the best way to do this is, |
| 431 | // but we need to figure this out to keep the same functionality. |
| 432 | |
| 433 | $items['project/autocomplete/project'] = array( |
| 434 | 'title' => 'Autocomplete project', |
| 435 | 'page callback' => 'project_autocomplete_project', |
| 436 | 'access callback' => 'project_project_access_any', |
| 437 | 'type' => MENU_CALLBACK, |
| 438 | ); |
| 439 | |
| 440 | $items['project/autocomplete/project-user/%'] = array( |
| 441 | 'title' => 'Autocomplete project', |
| 442 | 'page callback' => 'project_autocomplete_project_user', |
| 443 | 'page arguments' => array(3, 4), |
| 444 | 'access callback' => 'project_project_access_any', |
| 445 | 'type' => MENU_CALLBACK, |
| 446 | ); |
| 447 | |
| 448 | // CVS messages: |
| 449 | $items['project/cvs'] = array( |
| 450 | 'title' => 'CVS', |
| 451 | 'page callback' => 'project_cvs', |
| 452 | 'access callback' => 'project_project_access_any', |
| 453 | 'type' => MENU_CALLBACK, |
| 454 | ); |
| 455 | |
| 456 | // Administration pages |
| 457 | $items['admin/project'] = array( |
| 458 | 'title' => 'Project administration', |
| 459 | 'description' => 'Administrative interface for project management and related modules.', |
| 460 | 'page callback' => 'system_admin_menu_block_page', |
| 461 | 'access arguments' => array('administer projects'), |
| 462 | 'file' => 'system.admin.inc', |
| 463 | 'file path' => drupal_get_path('module', 'system'), |
| 464 | 'type' => MENU_NORMAL_ITEM, |
| 465 | ); |
| 466 | $items['admin/project/project-settings'] = array( |
| 467 | 'title' => 'Project settings', |
| 468 | 'description' => 'Configure settings for the Project module.', |
| 469 | 'page callback' => 'drupal_get_form', |
| 470 | 'page arguments' => array('project_settings_form'), |
| 471 | 'access arguments' => array('administer projects'), |
| 472 | 'type' => MENU_NORMAL_ITEM, |
| 473 | ); |
| 474 | |
| 475 | $items['node/%project_edit_project/edit/project'] = array( |
| 476 | 'title' => 'Project', |
| 477 | 'page callback' => 'node_page', |
| 478 | 'page arguments' => array(1), |
| 479 | 'access callback' => 'node_access', |
| 480 | 'access arguments' => array('update', 1), |
| 481 | 'weight' => -5, 'type' => MENU_DEFAULT_LOCAL_TASK, |
| 482 | ); |
| 483 | return $items; |
| 484 | } |
| 485 | |
| 486 | /** |
| 487 | * Menu loader callback to load a project node. |
| 488 | * |
| 489 | * @param $arg |
| 490 | * The menu argument to attempt to load a project from. Can be either a |
| 491 | * numeric node ID (nid), or a string project short name (uri). |
| 492 | * |
| 493 | * @return |
| 494 | * The loaded node object if the argument was a valid project, FALSE if not. |
| 495 | */ |
| 496 | function project_node_load($arg) { |
| 497 | if (is_numeric($arg)) { |
| 498 | $nid = $arg; |
| 499 | } |
| 500 | else { |
| 501 | $nid = db_result(db_query(db_rewrite_sql("SELECT p.nid FROM {project_projects} p WHERE p.uri = '%s'", 'p'), $arg)); |
| 502 | } |
| 503 | $node = node_load($nid); |
| 504 | if (!isset($node->type) || $node->type != 'project_project') { |
| 505 | return FALSE; |
| 506 | } |
| 507 | return $node; |
| 508 | } |
| 509 | |
| 510 | /** |
| 511 | * Menu loader callback to load a project node if the edit tab needs subtabs. |
| 512 | * |
| 513 | * Load a project_project node if the given nid is a project_project node and |
| 514 | * if the user has permission to edit the project and if either the |
| 515 | * project_issue or project_release module exists. |
| 516 | */ |
| 517 | function project_edit_project_load($nid) { |
| 518 | if (!module_exists('project_issue') && !module_exists('project_release')) { |
| 519 | return FALSE; |
| 520 | } |
| 521 | return project_node_load($nid); |
| 522 | } |
| 523 | |
| 524 | function project_check_admin_access($project, $cvs_access = NULL) { |
| 525 | global $user; |
| 526 | if (empty($user->uid)) { |
| 527 | return FALSE; |
| 528 | } |
| 529 | |
| 530 | $project_obj = is_numeric($project) ? node_load($project) : $project; |
| 531 | if (!isset($project_obj) || (isset($project_obj->type) && $project_obj->type != 'project_project')) { |
| 532 | return FALSE; |
| 533 | } |
| 534 | |
| 535 | if (user_access('administer projects')) { |
| 536 | return TRUE; |
| 537 | } |
| 538 | |
| 539 | // If $cvs_access is not defined, check to make sure the user has cvs access |
| 540 | // and that the user's cvs account is approved. |
| 541 | if (project_use_cvs($project_obj) && !isset($cvs_access)) { |
| 542 | if (db_result(db_query("SELECT COUNT(*) FROM {cvs_accounts} WHERE uid = %d AND status = %d", $user->uid, CVS_APPROVED))) { |
| 543 | $cvs_access = TRUE; |
| 544 | } |
| 545 | else { |
| 546 | $cvs_access = FALSE; |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | if (user_access('maintain projects')) { |
| 551 | if ($user->uid == $project_obj->uid) { |
| 552 | return TRUE; |
| 553 | } |
| 554 | if (project_use_cvs($project_obj) && $cvs_access) { |
| 555 | if (db_result(db_query("SELECT COUNT(*) FROM {cvs_project_maintainers} WHERE uid = %d AND nid = %d", $user->uid, $project_obj->nid))) { |
| 556 | return TRUE; |
| 557 | } |
| 558 | } |
| 559 | } |
| 560 | return FALSE; |
| 561 | } |
| 562 | |
| 563 | /** |
| 564 | * Implementation of form_alter. This removes the work of |
| 565 | * taxonomy.module's form_alter() so we can do our own taxonomy |
| 566 | * selection. |
| 567 | */ |
| 568 | function project_form_alter(&$form, &$form_state, $form_id) { |
| 569 | switch ($form_id) { |
| 570 | case 'project_project_node_form': |
| 571 | $vid = _project_get_vid(); |
| 572 | if (isset($form['taxonomy'][$vid])) { |
| 573 | unset($form['taxonomy'][$vid]); |
| 574 | } |
| 575 | // If there are no children elements, we should unset the entire |
| 576 | // thing so we don't end up with an empty fieldset. |
| 577 | if (!empty($form['taxonomy']) && (!element_children($form['taxonomy']))) { |
| 578 | unset($form['taxonomy']); |
| 579 | } |
| 580 | |
| 581 | // In D6, FAPI changed in such a way that completely unsetting |
| 582 | // $form['taxonomy'] causes warnings and errors when the project node is |
| 583 | // previewed. In order to prevent these problems, we need to add a |
| 584 | // submit handler to the preview button, and this handler needs to put |
| 585 | // the taxonomy terms back into $node->taxonomy like they would be if |
| 586 | // project wasn't replacing the usual taxonomy selection form elements |
| 587 | // with its own elements. |
| 588 | if (isset($form['buttons']['preview'])) { |
| 589 | $form['buttons']['preview']['#submit'][] = 'project_project_form_preview_submit'; |
| 590 | } |
| 591 | |
| 592 | // If the form has an element for specifying a URL alias, we want |
| 593 | // to alter it, since we're just going to automatically override |
| 594 | // the specified value. |
| 595 | if (variable_get('project_enable_alias', TRUE) && isset($form['path'])) { |
| 596 | $url_alias = $form['path']['path']['#default_value']; |
| 597 | if (empty($url_alias)) { |
| 598 | unset($form['path']); |
| 599 | } |
| 600 | else { |
| 601 | unset($form['path']['path']); |
| 602 | $form['path']['value'] = array( |
| 603 | '#prefix' => '<div>', |
| 604 | '#value' => t('Automatically generated path alias: %url', array('%url' => $url_alias)), |
| 605 | '#suffix' => '</div>', |
| 606 | ); |
| 607 | } |
| 608 | } |
| 609 | break; |
| 610 | |
| 611 | case 'taxonomy_form_vocabulary': |
| 612 | // Add a validate handler to the vocabulary form if the |
| 613 | // Project type vocabulary term is being edited so that its |
| 614 | // possible to detect if this vocabulary was set to be |
| 615 | // a free tagging vocabulary, and if so then throw an error. |
| 616 | $project_type_vid = _project_get_vid(); |
| 617 | if (isset($project_type_vid) && $form['vid']['#value'] == $project_type_vid) { |
| 618 | $form['#validate'][] = 'project_project_type_vocabulary_validate'; |
| 619 | |
| 620 | } |
| 621 | break; |
| 622 | } |
| 623 | } |
| 624 | |
| 625 | /** |
| 626 | * Implement hook_access(). |
| 627 | */ |
| 628 | function project_project_access($op, $node, $account) { |
| 629 | switch ($op) { |
| 630 | case 'view': |
| 631 | // Since this function is shared for project_release nodes, we have to |
| 632 | // be careful what node we pass to project_check_admin_access(). |
| 633 | if ($node->type == 'project_release') { |
| 634 | $node = node_load($node->project_release['pid']); |
| 635 | } |
| 636 | if (project_check_admin_access($node)) { |
| 637 | return TRUE; |
| 638 | } |
| 639 | if (!user_access('access projects')) { |
| 640 | return FALSE; |
| 641 | } |
| 642 | break; |
| 643 | case 'create': |
| 644 | if ($account->uid && user_access('maintain projects')) { |
| 645 | // Since this CVS access checking is non-standard, we need to |
| 646 | // special-case uid 1 to always allow everything. |
| 647 | if ($account->uid != 1 && module_exists('cvs') && variable_get('cvs_restrict_project_creation', 1)) { |
| 648 | return db_result(db_query("SELECT uid FROM {cvs_accounts} WHERE uid = %d AND status = %d", $account->uid, CVS_APPROVED)) ? TRUE : FALSE; |
| 649 | } |
| 650 | else { |
| 651 | return TRUE; |
| 652 | } |
| 653 | } |
| 654 | break; |
| 655 | case 'update': |
| 656 | if (project_check_admin_access($node)) { |
| 657 | return TRUE; |
| 658 | } |
| 659 | break; |
| 660 | case 'delete': |
| 661 | if (user_access('delete any projects', $account) || (user_access('delete own projects', $account) && ($account->uid == $node->uid))) { |
| 662 | return TRUE; |
| 663 | } |
| 664 | break; |
| 665 | } |
| 666 | } |
| 667 | |
| 668 | /** |
| 669 | * Implement hook_load(). |
| 670 | */ |
| 671 | function project_project_load($node) { |
| 672 | $additions = db_fetch_array(db_query('SELECT * FROM {project_projects} WHERE nid = %d', $node->nid)); |
| 673 | $project = new stdClass; |
| 674 | $project->project = $additions; |
| 675 | return $project; |
| 676 | } |
| 677 | |
| 678 | /** |
| 679 | * hook_nodeapi() implementation. This just decides what type of node |
| 680 | * is being passed, and calls the appropriate type-specific hook. |
| 681 | * |
| 682 | * @see project_project_nodeapi(). |
| 683 | */ |
| 684 | function project_nodeapi(&$node, $op, $arg) { |
| 685 | switch ($node->type) { |
| 686 | case 'project_project': |
| 687 | module_load_include('inc', 'project', 'project'); |
| 688 | project_project_nodeapi($node, $op, $arg); |
| 689 | break; |
| 690 | } |
| 691 | } |
| 692 | |
| 693 | /** |
| 694 | * Build a SQL statment from a structured array. |
| 695 | * |
| 696 | * @param $sql_elements |
| 697 | * An array with the following keys: |
| 698 | * 'fields', 'from', 'joins', 'wheres', 'group_bys', 'order_bys', |
| 699 | * 'parameters' |
| 700 | * Each value is an array with the following keys: |
| 701 | * 'prefix', 'glue', 'pieces' |
| 702 | * @return |
| 703 | * SQL string. |
| 704 | * |
| 705 | * @TODO This should move to project_usage.module |
| 706 | */ |
| 707 | function project_build_query($sql_elements) { |
| 708 | $sql = ''; |
| 709 | foreach ($sql_elements as $key => $sql_element) { |
| 710 | if ($key != 'parameters' && count($sql_element['pieces'])) { |
| 711 | $sql .= $sql_element['prefix'] . implode($sql_element['glue'], $sql_element['pieces']); |
| 712 | } |
| 713 | } |
| 714 | return db_rewrite_sql($sql); |
| 715 | } |
| 716 | |
| 717 | /** |
| 718 | * Construct an empty query for project_build_query(). |
| 719 | * |
| 720 | * Set the default elements that will be used to construct the SQL statement. |
| 721 | * |
| 722 | * @TODO This should move to project_usage.module |
| 723 | */ |
| 724 | function project_empty_query() { |
| 725 | return array( |
| 726 | 'fields' => array( |
| 727 | 'prefix' => 'SELECT ', |
| 728 | 'glue' => ', ', |
| 729 | 'pieces' => array(), |
| 730 | ), |
| 731 | 'from' => array( |
| 732 | 'prefix' => ' FROM ', |
| 733 | 'glue' => NULL, |
| 734 | 'pieces' => array(''), |
| 735 | ), |
| 736 | 'joins' => array( |
| 737 | 'prefix' => '', |
| 738 | 'glue' => ' ', |
| 739 | 'pieces' => array(), |
| 740 | ), |
| 741 | 'wheres' => array( |
| 742 | 'prefix' => ' WHERE ', |
| 743 | 'glue' => ' AND ', |
| 744 | 'pieces' => array(), |
| 745 | ), |
| 746 | 'group_bys' => array( |
| 747 | 'prefix' => ' GROUP BY ', |
| 748 | 'glue' => ', ', |
| 749 | 'pieces' => array(), |
| 750 | ), |
| 751 | 'order_bys' => array( |
| 752 | 'prefix' => ' ORDER BY ', |
| 753 | 'glue' => ', ', |
| 754 | 'pieces' => array(), |
| 755 | ), |
| 756 | 'parameters' => array( |
| 757 | 'prefix' => NULL, |
| 758 | 'glue' => NULL, |
| 759 | 'pieces' => array(), |
| 760 | ) |
| 761 | ); |
| 762 | } |
| 763 | |
| 764 | /** |
| 765 | * Helper function for grouping nodes by date. |
| 766 | */ |
| 767 | function _project_date($timestamp) { |
| 768 | static $last; |
| 769 | $date = format_date($timestamp, 'custom', 'F j, Y'); |
| 770 | if ($date != $last) { |
| 771 | $last = $date; |
| 772 | return $date; |
| 773 | } |
| 774 | } |
| 775 | |
| 776 | /** |
| 777 | * Returns an array of all projects on the system for use with select |
| 778 | * form elements. The keys are the project nid, and the values are |
| 779 | * the project names. If the project taxonomy is in use, the array |
| 780 | * will be sorted into the project categories with appropriate headers |
| 781 | * for each term. |
| 782 | * |
| 783 | * @param $project_urls |
| 784 | * Reference to be filled with an array of project uri => id mappings. This |
| 785 | * array is used by the project_issue search form code. |
| 786 | * @param $issues |
| 787 | * If TRUE, only projects with issue tracking enabled are returned. |
| 788 | * For this option to do much good, the project_issue.module should be |
| 789 | * enabled. |
| 790 | * @param $key_prefix |
| 791 | * Prefix to prepend to all keys in the returned array. |
| 792 | */ |
| 793 | function project_projects_select_options(&$project_urls, $issues = TRUE, $key_prefix = NULL) { |
| 794 | $projects = array(); |
| 795 | $ISSUE_JOIN = ''; |
| 796 | $ISSUE_WHERE = ''; |
| 797 | $args_use_taxonomy = array(1, 0); // n.status, h.parent |
| 798 | $args_no_taxonomy = array(1); // n.status |
| 799 | if ($issues && module_exists('project_issue')) { |
| 800 | $ISSUE_JOIN ='INNER JOIN {project_issue_projects} pip ON n.nid = pip.nid'; |
| 801 | $ISSUE_WHERE = 'AND pip.issues = %d'; |
| 802 | $args_use_taxonomy[] = 1; |
| 803 | $args_no_taxonomy[] = 1; |
| 804 | } |
| 805 | if (project_use_taxonomy()) { |
| 806 | $vid = _project_get_vid(); |
| 807 | $args_use_taxonomy[] = $vid; |
| 808 | $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.vid = n.vid INNER JOIN {term_data} d ON t.tid = d.tid INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE n.status = %d AND h.parent = %d $ISSUE_WHERE AND d.vid = %d GROUP BY n.nid, n.title, d.name, p.uri, d.weight ORDER BY d.weight, n.title"), $args_use_taxonomy); |
| 809 | while ($project = db_fetch_object($result)) { |
| 810 | if (isset($project->name)) { |
| 811 | if (!isset($projects[$project->name])) { |
| 812 | $projects[$project->name] = array(); |
| 813 | } |
| 814 | $projects[$project->name][$key_prefix . $project->nid] = $project->title; |
| 815 | } |
| 816 | else { |
| 817 | $projects[$key_prefix . $project->nid] = $project->title; |
| 818 | } |
| 819 | if (is_array($project_urls)) { |
| 820 | $project_urls[$project->uri] = $project->nid; |
| 821 | } |
| 822 | } |
| 823 | } |
| 824 | else { |
| 825 | $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 = %d $ISSUE_WHERE ORDER BY n.title"), $args_no_taxonomy); |
| 826 | while ($project = db_fetch_object($result)) { |
| 827 | $projects[$key_prefix . $project->nid] = $project->title; |
| 828 | if (is_array($project_urls)) { |
| 829 | $project_urls[$project->uri] = $project->nid; |
| 830 | } |
| 831 | } |
| 832 | } |
| 833 | return $projects; |
| 834 | } |
| 835 | |
| 836 | function project_quick_navigate_form() { |
| 837 | $uris = NULL; |
| 838 | $projects = array(t('- Select a project -')) + project_projects_select_options($uris, FALSE); |
| 839 | $form = array(); |
| 840 | $form['project_goto'] = array( |
| 841 | '#type' => 'select', |
| 842 | '#default_value' => '0', |
| 843 | '#options' => $projects, |
| 844 | '#attributes' => array('title' => t('Select a project to view')), |
| 845 | ); |
| 846 | $form['project'] = array( |
| 847 | '#type' => 'submit', |
| 848 | '#value' => t('View project'), |
| 849 | '#submit' => array('project_quick_navigate_project_submit'), |
| 850 | ); |
| 851 | return $form; |
| 852 | } |
| 853 | |
| 854 | function project_quick_navigate_form_validate($form, $form_state) { |
| 855 | if (empty($form_state['values']['project_goto'])) { |
| 856 | form_set_error('project_goto', t('You must select a project to navigate to.')); |
| 857 | } |
| 858 | } |
| 859 | |
| 860 | function project_quick_navigate_project_submit($form, &$form_state) { |
| 861 | $form_state['redirect'] = 'node/'. $form_state['values']['project_goto']; |
| 862 | } |
| 863 | |
| 864 | function project_quick_navigate_title_form() { |
| 865 | $form = array(); |
| 866 | $form['project_title'] = array( |
| 867 | '#title' => t('Project name'), |
| 868 | '#type' => 'textfield', |
| 869 | '#size' => 20, |
| 870 | '#autocomplete_path' => 'project/autocomplete/project', |
| 871 | ); |
| 872 | $form['project'] = array( |
| 873 | '#type' => 'submit', |
| 874 | '#value' => t('View project'), |
| 875 | '#validate' => array('project_quick_navigate_title_project_validate'), |
| 876 | '#submit' => array('project_quick_navigate_title_project_submit'), |
| 877 | ); |
| 878 | return $form; |
| 879 | } |
| 880 | |
| 881 | function project_quick_navigate_title_project_validate($form, &$form_state) { |
| 882 | if (empty($form_state['values']['project_title'])) { |
| 883 | form_set_error('project_title', t('You must enter a project to navigate to.')); |
| 884 | } |
| 885 | else { |
| 886 | $nid = db_result(db_query("SELECT nid FROM {node} WHERE title = '%s' AND type = '%s'", $form_state['values']['project_title'], 'project_project')); |
| 887 | if (empty($nid)) { |
| 888 | form_set_error('project_title', t('The name you entered (%title) is not a valid project.', array('%title' => $form_state['values']['project_title']))); |
| 889 | } |
| 890 | else { |
| 891 | $form_state['nid'] = $nid; |
| 892 | } |
| 893 | } |
| 894 | } |
| 895 | |
| 896 | function project_quick_navigate_title_project_submit($form, &$form_state) { |
| 897 | $form_state['redirect'] = 'node/'. $form_state['nid']; |
| 898 | } |
| 899 | |
| 900 | |
| 901 | /** |
| 902 | * Implementation of hook_views_api(). |
| 903 | */ |
| 904 | function project_views_api() { |
| 905 | return array( |
| 906 | 'api' => 2, |
| 907 | 'path' => drupal_get_path('module', 'project') .'/views', |
| 908 | ); |
| 909 | } |
| 910 | |
| 911 | // Themables |
| 912 | |
| 913 | /** |
| 914 | * Implementation of hook_theme(). |
| 915 | */ |
| 916 | function project_theme() { |
| 917 | return array( |
| 918 | 'project_term_list' => array( |
| 919 | 'arguments' => array( |
| 920 | 'terms' => NULL, |
| 921 | 'path' => NULL, |
| 922 | ), |
| 923 | ), |
| 924 | 'project_summary' => array( |
| 925 | 'arguments' => array( |
| 926 | 'project' => NULL, |
| 927 | ), |
| 928 | ), |
| 929 | 'project_feed_icon' => array( |
| 930 | 'arguments' => array( |
| 931 | 'url' => NULL, |
| 932 | 'title' => NULL, |
| 933 | ), |
| 934 | ), |
| 935 | 'project_project_node_form_taxonomy' => array( |
| 936 | 'file' => 'project.inc', |
| 937 | 'arguments' => array( |
| 938 | 'form' => NULL, |
| 939 | ), |
| 940 | ), |
| 941 | 'project_views_project_sort_method_options_form' => array( |
| 942 | 'file' => 'theme.inc', |
| 943 | 'path' => drupal_get_path('module', 'project') .'/views/theme', |
| 944 | 'arguments' => array( |
| 945 | 'form' => NULL, |
| 946 | ), |
| 947 | ), |
| 948 | 'project_type' => array( |
| 949 | 'arguments' => array( |
| 950 | 'term' => NULL, |
| 951 | ), |
| 952 | ), |
| 953 | 'project_type_description' => array( |
| 954 | 'arguments' => array( |
| 955 | 'term' => NULL, |
| 956 | ), |
| 957 | ), |
| 958 | ); |
| 959 | } |
| 960 | |
| 961 | function theme_project_term_list($terms, $path) { |
| 962 | $depth = 0; |
| 963 | $output = "\n<ul class=\"project-terms\">\n"; |
| 964 | foreach ($terms as $term) { |
| 965 | if (!empty($term->description)){ |
| 966 | $description = strip_tags($term->description); |
| 967 | } |
| 968 | else { |
| 969 | $description = ''; |
| 970 | } |
| 971 | $link = l($term->name .' ('. $term->count .')', "$path/$term->tid", array('attributes' => array('title' => $description))); |
| 972 | $output .= '<li class="leaf">' . $link . "</li>\n"; |
| 973 | } |
| 974 | $output .= "\n</ul>\n"; |
| 975 | return $output; |
| 976 | } |
| 977 | |
| 978 | /** |
| 979 | * Theme a compact project view/summary. |
| 980 | * |
| 981 | * $project has the following fields: |
| 982 | * - title: Title |
| 983 | * - nid: Node id |
| 984 | * - body: Filtered description |
| 985 | * - term: String with the selected term name |
| 986 | * - version: String with the version |
| 987 | * - links: Array of links |
| 988 | */ |
| 989 | function theme_project_summary($project) { |
| 990 | $output = '<div class="' . $project->class . '">'; |
| 991 | $output .= '<h2>'. l($project->title, "node/$project->nid") .'</h2>'; |
| 992 | if (!empty($project->changed)) { |
| 993 | $output .= '<p><small>' . t('Last changed: !interval ago', array('!interval' => format_interval(time() - $project->changed, 2))) . '</small></p>'; |
| 994 | } |
| 995 | $output .= $project->body; |
| 996 | if (!empty($project->download_table)) { |
| 997 | $output .= $project->download_table; |
| 998 | } |
| 999 | if (!empty($project->links)) { |
| 1000 | $output .= theme('links', $project->links); |
| 1001 | } |
| 1002 | if (!empty($project->terms)) { |
| 1003 | $output .= theme('links', $project->terms); |
| 1004 | } |
| 1005 | $output .= '</div>'; |
| 1006 | return $output; |
| 1007 | } |
| 1008 | |
| 1009 | /** |
| 1010 | * Display an RSS feed icon for use on project nodes. |
| 1011 | */ |
| 1012 | function theme_project_feed_icon($url, $title) { |
| 1013 | if ($icon = theme('image', 'misc/feed.png', $title, $title)) { |
| 1014 | return '<a href="'. check_url($url) .'" class="project-feed-icon">'. $icon .'</a>'; |
| 1015 | } |
| 1016 | } |
| 1017 | |
| 1018 | /** |
| 1019 | * Return a string of valid project names for use with JS autocomplete fields. |
| 1020 | */ |
| 1021 | function project_autocomplete_project($string = '') { |
| 1022 | $matches = array(); |
| 1023 | |
| 1024 | // The user enters a comma-separated list of projects. We only autocomplete |
| 1025 | // the last one. |
| 1026 | $array = drupal_explode_tags($string); |
| 1027 | $last_string = trim(array_pop($array)); |
| 1028 | |
| 1029 | if ($last_string != '') { |
| 1030 | $result = db_query_range(db_rewrite_sql("SELECT n.title FROM {node} n WHERE n.status = %d AND n.type = '%s' AND LOWER(n.title) LIKE LOWER('%%%s%%')"), 1, 'project_project', $last_string, 0, 10); |
| 1031 | |
| 1032 | $prefix = count($array) ? implode(', ', $array) .', ' : ''; |
| 1033 | while ($project = db_fetch_object($result)) { |
| 1034 | $title = $project->title; |
| 1035 | // Commas and quotes in terms are special cases, so encode 'em. |
| 1036 | if (strpos($title, ',') !== FALSE || strpos($title, '"') !== FALSE) { |
| 1037 | $title = '"'. str_replace('"', '""', $project->title) .'"'; |
| 1038 | } |
| 1039 | $matches[$prefix . $title] = check_plain($project->title); |
| 1040 | } |
| 1041 | } |
| 1042 | |
| 1043 | drupal_json($matches); |
| 1044 | } |
| 1045 | |
| 1046 | |
| 1047 | /** |
| 1048 | * Return a string of valid project names owned by a given user. |
| 1049 | */ |
| 1050 | function project_autocomplete_project_user($uid, $string = '') { |
| 1051 | $matches = array(); |
| 1052 | |
| 1053 | // The user enters a comma-separated list of projects. We only autocomplete |
| 1054 | // the last one. |
| 1055 | $array = drupal_explode_tags($string); |
| 1056 | $last_string = trim(array_pop($array)); |
| 1057 | |
| 1058 | if ($last_string != '') { |
| 1059 | $result = db_query_range(db_rewrite_sql("SELECT n.title FROM {node} n WHERE n.status = %d AND n.type = '%s' AND n.uid = %d AND LOWER(n.title) LIKE LOWER('%%%s%%')"), 1, 'project_project', $uid, $last_string, 0, 10); |
| 1060 | |
| 1061 | $prefix = count($array) ? implode(', ', $array) .', ' : ''; |
| 1062 | while ($project = db_fetch_object($result)) { |
| 1063 | $title = $project->title; |
| 1064 | // Commas and quotes in terms are special cases, so encode 'em. |
| 1065 | if (strpos($title, ',') !== FALSE || strpos($title, '"') !== FALSE) { |
| 1066 | $title = '"'. str_replace('"', '""', $project->title) .'"'; |
| 1067 | } |
| 1068 | $matches[$prefix . $title] = check_plain($project->title); |
| 1069 | } |
| 1070 | } |
| 1071 | |
| 1072 | drupal_json($matches); |
| 1073 | } |
| 1074 | |
| 1075 | /** |
| 1076 | * Returns whether or not the project module should use |
| 1077 | * taxonomy-specific functionality. |
| 1078 | */ |
| 1079 | function project_use_taxonomy() { |
| 1080 | return module_exists('taxonomy') && taxonomy_get_tree(_project_get_vid()); |
| 1081 | } |
| 1082 | |
| 1083 | /** |
| 1084 | * Returns whether or not the project uses version control or not. |
| 1085 | */ |
| 1086 | function project_use_cvs($project) { |
| 1087 | if (module_exists('cvs')) { |
| 1088 | $project = is_numeric($project) ? node_load($project) : $project; |
| 1089 | return !empty($project->cvs['repository']); |
| 1090 | } |
| 1091 | } |
| 1092 | |
| 1093 | /** |
| 1094 | * Determines if the current site supports caching of project-related pages. |
| 1095 | * |
| 1096 | * The pages can be cached if: |
| 1097 | * 1. Anonymous users have the 'access projects' permission. |
| 1098 | * 2. No node access modules are installed. |
| 1099 | * |
| 1100 | * @return TRUE if the output can be cached, FALSE otherwise. |
| 1101 | */ |
| 1102 | function project_can_cache() { |
| 1103 | $grants = module_implements('node_grants'); |
| 1104 | if (!empty($grants)) { |
| 1105 | return FALSE; |
| 1106 | } |
| 1107 | $allowed_roles = user_roles(FALSE, 'access projects'); |
| 1108 | if (!isset($allowed_roles[DRUPAL_ANONYMOUS_RID])) { |
| 1109 | return FALSE; |
| 1110 | } |
| 1111 | |
| 1112 | return TRUE; |
| 1113 | } |
| 1114 | |
| 1115 | /** |
| 1116 | * Intelligently merge project type terms selected by the |
| 1117 | * user back into $node->taxonomy during a node preview. |
| 1118 | */ |
| 1119 | function project_project_form_preview_submit($form, &$form_state) { |
| 1120 | $project_type_vid = _project_get_vid(); |
| 1121 | if (isset($project_type_vid) && isset($form_state['values']['project_type'])) { |
| 1122 | $project_type_tid = $form_state['values']['project_type']; |
| 1123 | $project_type_tid_field = 'tid_' . $project_type_tid; |
| 1124 | $project_type_terms = array(); |
| 1125 | $project_type_terms[$project_type_tid] = $project_type_tid; |
| 1126 | if (isset($form_state['values']->$project_type_tid_field)) { |
| 1127 | foreach ($form_state['values'][$project_type_tid_field] as $tid) { |
| 1128 | $project_type_terms[$tid] = $tid; |
| 1129 | } |
| 1130 | } |
| 1131 | $form_state['node']['taxonomy'][$project_type_vid] = $project_type_terms; |
| 1132 | } |
| 1133 | } |
| 1134 | |
| 1135 | /** |
| 1136 | * Present a validation error if the user tries to make the |
| 1137 | * Project types vocabulary a free tagging vocabulary. |
| 1138 | */ |
| 1139 | function project_project_type_vocabulary_validate($form, &$form_state) { |
| 1140 | if (!empty($form_state['values']['tags'])) { |
| 1141 | form_set_error('tags', t('The %name vocabulary does not support tags.', array('%name' => $form_state['values']['name']))); |
| 1142 | } |
| 1143 | } |
| 1144 | |
| 1145 | /** |
| 1146 | * Theme a project type item. |
| 1147 | */ |
| 1148 | function theme_project_type($term) { |
| 1149 | $link = l($term->name, check_url('project/'. drupal_strtolower($term->name))); |
| 1150 | $link = str_replace('%20', '+', $link); |
| 1151 | $output = "<div>$link</div>\n"; |
| 1152 | if ($term->description) { |
| 1153 | $output .= '<p>' . filter_xss($term->description) . '</p>'; |
| 1154 | } |
| 1155 | return $output; |
| 1156 | } |
| 1157 | |
| 1158 | /** |
| 1159 | * Theme a project type description. |
| 1160 | */ |
| 1161 | function theme_project_type_description($term) { |
| 1162 | $output = ''; |
| 1163 | if ($term->description) { |
| 1164 | $output .= '<p>' . filter_xss($term->description) . '</p>'; |
| 1165 | } |
| 1166 | return $output; |
| 1167 | } |
| 1168 | |
| 1169 | |
| 1170 | /** |
| 1171 | * Find a project node ID (nid) based on project short name (uri). |
| 1172 | * |
| 1173 | * @param $uri |
| 1174 | * The project shortname (uri) to search for. |
| 1175 | * |
| 1176 | * @return |
| 1177 | * The project node ID (nid) of the given project, or NULL if not found. |
| 1178 | */ |
| 1179 | function project_get_nid_from_uri($uri) { |
| 1180 | static $nids = array(); |
| 1181 | if (!isset($nids[$uri])) { |
| 1182 | $nids[$uri] = db_result(db_query_range("SELECT nid FROM {project_projects} WHERE uri = '%s'", $uri, 0, 1)); |
| 1183 | } |
| 1184 | return $nids[$uri]; |
| 1185 | } |
| 1186 | |
| 1187 | /** |
| 1188 | * Find a project shortname (uri) based on project node ID (nid). |
| 1189 | * |
| 1190 | * @param $uri |
| 1191 | * The project node ID (nid) to search for. |
| 1192 | * |
| 1193 | * @return |
| 1194 | * The project shortname (uri) of the given project, or NULL if not found. |
| 1195 | */ |
| 1196 | function project_get_uri_from_nid($nid) { |
| 1197 |