| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: project.module,v 1.356 2010/01/30 02:43:23 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) && !empty($form['vid']['#value']) && $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 static $uris = array(); 1198 if (!isset($uris[$nid])) { 1199 $uris[$nid] = db_result(db_query_range("SELECT uri FROM {project_projects} WHERE nid = %d", $nid, 0, 1)); 1200 } 1201 return $uris[$nid]; 1202 } 1203 1204 /** 1205 * Get the project node context from the currently active menu, if any. 1206 * 1207 * @return 1208 * A fully loaded project $node object if the currently active menu has a 1209 * project node context, or NULL if the menu isn't pointing to a project. 1210 */ 1211 function project_get_project_from_menu() { 1212 if (($node = menu_get_object('project_node')) || 1213 (($node = menu_get_object()) && $node->type == 'project_project')) { 1214 return $node; 1215 } 1216 } 1217 1218 /** 1219 * Implement hook_token_list() (from token.module) 1220 */ 1221 function project_token_list($type) { 1222 if ($type == 'node') { 1223 $tokens['node'] = array( 1224 'project_shortname' => t("A project's short name"), 1225 'project_homepage' => t("Link to a project's home page"), 1226 'project_changelog' => t("Link to a project's changelog"), 1227 'project_cvs' => t("Link to a project's cvs tree"), 1228 'project_demo' => t('Demonstration link for a project'), 1229 'project_documentation' => t("Link to a project's documentation"), 1230 'project_screenshots' => t("Link to a project's screenshots"), 1231 'project_license' => t("Link to a project's license"), 1232 ); 1233 return $tokens; 1234 } 1235 } 1236 1237 /** 1238 * Implement hook_token_values() (from token.module) 1239 */ 1240 function project_token_values($type = 'all', $object = NULL) { 1241 if ($type == 'node') { 1242 if ($object->type == 'project_project') { 1243 $values['project_shortname'] = check_plain($object->project['uri']); 1244 $values['project_homepage'] = check_url($object->project['homepage']); 1245 $values['project_changelog'] = check_url($object->project['changelog']); 1246 $values['project_cvs'] = check_url($object->project['cvs']); 1247 $values['project_demo'] = check_url($object->project['demo']); 1248 $values['project_documentation'] = check_url($object->project['documentation']); 1249 $values['project_screenshots'] = check_url($object->project['screenshots']); 1250 $values['project_license'] = check_url($object->project['license']); 1251 } 1252 else { 1253 $values['project_shortname'] = ''; 1254 $values['project_homepage'] = ''; 1255 $values['project_changelog'] = ''; 1256 $values['project_cvs'] = ''; 1257 $values['project_demo'] = ''; 1258 $values['project_documentation'] = ''; 1259 $values['project_screenshots'] = ''; 1260 $values['project_license'] = ''; 1261 } 1262 return $values; 1263 } 1264 } 1265
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |