| [ Index ] |
PHP Cross Reference of Drupal 6 (yi-drupal) |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @defgroup pathauto Pathauto: Automatically generates aliases for content 5 * 6 * The Pathauto module automatically generates path aliases for various kinds of 7 * content (nodes, categories, users) without requiring the user to manually 8 * specify the path alias. This allows you to get aliases like 9 * /category/my-node-title.html instead of /node/123. The aliases are based upon 10 * a "pattern" system which the administrator can control. 11 */ 12 13 /** 14 * @file 15 * Main file for the Pathauto module, which automatically generates aliases for content. 16 * 17 * @ingroup pathauto 18 */ 19 20 /** 21 * Implements hook_help(). 22 */ 23 function pathauto_help($path, $arg) { 24 switch ($path) { 25 case 'admin/help#pathauto': 26 $output = t('<p>Provides a mechanism for modules to automatically generate aliases for the content they manage.</p> 27 <h2>Settings</h2> 28 <p>The <strong>Maximum Alias Length</strong> and <strong>Maximum component length</strong> values 29 default to 100 and have a limit of 128 from pathauto. This length is limited by the length of the dst 30 column of the url_alias database table. The default database schema for this column is 128. If you 31 set a length that is equal to that of the one set in the dst column it will cause problems in situations 32 where the system needs to append additional words to the aliased URL. For example... URLs generated 33 for feeds will have "/feed" added to the end. You should enter a value that is the length of the dst 34 column minus the length of any strings that might get added to the end of the URL. The length of 35 strings that might get added to the end of your URLs depends on which modules you have enabled and 36 on your Pathauto settings. The recommended and default value is 100.</p> 37 <p><strong>Raw Tokens</strong> In Pathauto it is appropriate to use the -raw form of tokens. Paths are 38 sent through a filtering system which ensures that raw user content is filtered. Failure to use -raw 39 tokens can cause problems with the Pathauto punctuation filtering system.</p>'); 40 return $output; 41 } 42 } 43 44 /** 45 * Implements hook_perm(). 46 */ 47 function pathauto_perm() { 48 return array( 49 'administer pathauto', 50 'notify of path changes', 51 ); 52 } 53 54 /** 55 * Implements hook_menu(). 56 */ 57 function pathauto_menu() { 58 $items['admin/build/path/pathauto'] = array( 59 'title' => 'Automated alias settings', 60 'page callback' => 'drupal_get_form', 61 'page arguments' => array('pathauto_admin_settings'), 62 'access arguments' => array('administer pathauto'), 63 'type' => MENU_LOCAL_TASK, 64 'weight' => 10, 65 'file' => 'pathauto.admin.inc', 66 ); 67 68 $items['admin/build/path/delete_bulk'] = array( 69 'title' => 'Delete aliases', 70 'page callback' => 'drupal_get_form', 71 'page arguments' => array('pathauto_admin_delete'), 72 'access arguments' => array('administer url aliases'), 73 'type' => MENU_LOCAL_TASK, 74 'weight' => 30, 75 'file' => 'pathauto.admin.inc', 76 ); 77 78 return $items; 79 } 80 81 /** 82 * Include all Pathauto include files. 83 */ 84 function _pathauto_include() { 85 module_load_include('inc', 'pathauto'); 86 module_load_include('inc', 'pathauto', 'pathauto_node'); 87 module_load_include('inc', 'pathauto', 'pathauto_taxonomy'); 88 module_load_include('inc', 'pathauto', 'pathauto_user'); 89 } 90 91 /** 92 * Implements hook_token_list(). 93 */ 94 function pathauto_token_list($type = 'all') { 95 $tokens = array(); 96 if (module_exists('taxonomy')) { 97 if ($type == 'taxonomy' || $type == 'all') { 98 $tokens['taxonomy']['catpath'] = t('As [cat], but including its supercategories separated by /.'); 99 $tokens['taxonomy']['catpath-raw'] = t('As [cat-raw], but including its supercategories separated by /.'); 100 $tokens['taxonomy']['catalias'] = t('The URL alias of the taxonomy term.'); 101 $tokens['taxonomy']['catalias-raw'] = t('The URL alias of the taxonomy term.'); 102 } 103 if ($type == 'node' || $type == 'all') { 104 $tokens['node']['termpath'] = t('As [term], but including its supercategories separated by /.'); 105 $tokens['node']['termpath-raw'] = t('As [term-raw], but including its supercategories separated by /.'); 106 $tokens['node']['termalias'] = t('The URL alias of the taxonomy term.'); 107 $tokens['node']['termalias-raw'] = t('The URL alias of the taxonomy term.'); 108 } 109 } 110 if (module_exists('book')) { 111 if ($type == 'node' || $type == 'all') { 112 $tokens['node']['bookpathalias'] = t('The URL alias of the parent book of the node.'); 113 $tokens['node']['bookpathalias-raw'] = t('The URL alias of the parent book of the node.'); 114 } 115 } 116 return $tokens; 117 } 118 119 /** 120 * Implements hook_token_values(). 121 */ 122 function pathauto_token_values($type, $object = NULL, $options = array(), $label = NULL) { 123 $values = array(); 124 125 switch ($type) { 126 case 'node': 127 // Token [bookpathalias]. 128 if (module_exists('book')) { 129 $values['bookpathalias'] = ''; 130 $values['bookpathalias-raw'] = ''; 131 if (!empty($object->book['plid']) && $parent = book_link_load($object->book['plid'])) { 132 $values['bookpathalias-raw'] = drupal_get_path_alias($parent['href']); 133 $values['bookpathalias'] = check_plain($values['bookpathalias-raw']); 134 } 135 } 136 137 // Tokens [termpath], [termpath-raw], and [termalias]. 138 if (module_exists('taxonomy')) { 139 // Get the lowest-weighted term from the lowest-weighted vocabulary. 140 // This query is copied from @taxonomy_node_get_terms() 141 $term = db_fetch_object(db_query_range('SELECT t.* FROM {term_node} r INNER JOIN {term_data} t ON r.tid = t.tid INNER JOIN {vocabulary} v ON t.vid = v.vid WHERE r.vid = %d ORDER BY v.weight, t.weight, t.name', $object->vid, 0, 1)); 142 if ($term) { 143 $values = array_merge($values, pathauto_token_values('taxonomy', $term, $options, 'term')); 144 } 145 else { 146 $values['termpath'] = $values['termpath-raw'] = $values['termalias'] = ''; 147 } 148 } 149 break; 150 151 case 'taxonomy': 152 // In the realm of nodes these are 'terms', in the realm of taxonomy, 'cats'. 153 if (!isset($label)) { 154 $label = 'cat'; 155 } 156 157 $values[$label . 'path'] = ''; 158 $values[$label . 'path-raw'] = ''; 159 $values[$label . 'alias'] = ''; 160 $values[$label . 'alias-raw'] = ''; 161 162 // Tokens [catpath] and [catpath-raw]. 163 if (isset($object->tid)) { 164 $parents = taxonomy_get_parents_all($object->tid); 165 $catpath = $catpath_raw = array(); 166 foreach ($parents as $parent) { 167 array_unshift($catpath, check_plain($parent->name)); 168 array_unshift($catpath_raw, $parent->name); 169 } 170 $values[$label . 'path'] = !empty($options['pathauto']) ? $catpath : implode('/', $catpath); 171 $values[$label . 'path-raw'] = !empty($options['pathauto']) ? $catpath_raw : implode('/', $catpath_raw); 172 173 // Token [catalias-raw] and [catalias]. 174 $values[$label . 'alias-raw'] = drupal_get_path_alias(taxonomy_term_path($object)); 175 $values[$label . 'alias'] = check_plain($values[$label . 'alias-raw']); 176 } 177 break; 178 } 179 180 return $values; 181 } 182 183 /** 184 * Implementation of hook_path_alias_types(). 185 * 186 * Used primarily by the bulk delete form. 187 */ 188 function pathauto_path_alias_types() { 189 $objects['user/'] = t('Users'); 190 $objects['node/'] = t('Content'); 191 if (module_exists('blog')) { 192 $objects['blog/'] = t('User blogs'); 193 } 194 if (module_exists('taxonomy')) { 195 $objects['taxonomy/term/'] = t('Taxonomy terms'); 196 } 197 if (module_exists('forum')) { 198 $objects['forum/'] = t('Forums'); 199 } 200 if (module_exists('contact')) { 201 $objects['user/%/contact'] = t('User contact forms'); 202 } 203 if (module_exists('tracker')) { 204 $objects['user/%/track'] = t('User trackers'); 205 } 206 return $objects; 207 } 208 209 /** 210 * Return the proper SQL to perform cross-db and field-type concatenation. 211 * 212 * @return 213 * A string of SQL with the concatenation. 214 */ 215 function _pathauto_sql_concat() { 216 $args = func_get_args(); 217 switch ($GLOBALS['db_type']) { 218 case 'mysql': 219 case 'mysqli': 220 return 'CONCAT(' . implode(', ', $args) . ')'; 221 default: 222 // The ANSI standard of concatentation uses the double-pipe. 223 return '(' . implode(' || ', $args) . ')'; 224 } 225 } 226 227 /** 228 * Implements hook_field_attach_rename_bundle(). 229 * 230 * Respond to machine name changes for pattern variables. 231 */ 232 function pathauto_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { 233 $result = db_query("SELECT name FROM {variable} WHERE name LIKE '%s%%'", "pathauto_{$entity_type}_{$bundle_old}_"); 234 while ($variable = db_result($result)) { 235 $value = variable_get($variable, ''); 236 variable_del($variable); 237 $variable = strtr($variable, array("{$entity_type}_{$bundle_old}" => "{$entity_type}_{$bundle_new}")); 238 variable_set($variable, $value); 239 } 240 } 241 242 /** 243 * Implements hook_field_attach_delete_bundle(). 244 * 245 * Respond to sub-types being deleted, their patterns can be removed. 246 */ 247 function pathauto_field_attach_delete_bundle($entity_type, $bundle) { 248 $result = db_query("SELECT name FROM {variable} WHERE name LIKE '%s%%'", "pathauto_{$entity_type}_{$bundle}_"); 249 while ($variable = db_result($result)) { 250 variable_del($variable); 251 } 252 } 253 254 //============================================================================== 255 // Some node related functions. 256 257 /** 258 * Implements hook_nodeapi(). 259 */ 260 function pathauto_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { 261 switch ($op) { 262 case 'presave': 263 // About to be saved (before insert/update) 264 if (!empty($node->pathauto_perform_alias) && isset($node->old_alias) 265 && $node->path == '' && $node->old_alias != '') { 266 /** 267 * There was an old alias, but when pathauto_perform_alias was checked 268 * the javascript disabled the textbox which led to an empty value being 269 * submitted. Restoring the old path-value here prevents the Path module 270 * from deleting any old alias before Pathauto gets control. 271 */ 272 $node->path = $node->old_alias; 273 } 274 break; 275 case 'insert': 276 case 'update': 277 // Skip processing if the user has disabled pathauto for the node. 278 if (isset($node->pathauto_perform_alias) && empty($node->pathauto_perform_alias)) { 279 return; 280 } 281 282 _pathauto_include(); 283 // Get the specific pattern or the default 284 if (variable_get('language_content_type_'. $node->type, 0)) { 285 $pattern = trim(variable_get('pathauto_node_'. $node->type .'_'. $node->language .'_pattern', FALSE)); 286 } 287 if (empty($pattern)) { 288 $pattern = trim(variable_get('pathauto_node_'. $node->type .'_pattern', FALSE)); 289 if (empty($pattern)) { 290 $pattern = trim(variable_get('pathauto_node_pattern', FALSE)); 291 } 292 } 293 // Only do work if there's a pattern 294 if ($pattern) { 295 $placeholders = pathauto_get_placeholders('node', $node); 296 $src = "node/$node->nid"; 297 if ($alias = pathauto_create_alias('node', $op, $placeholders, $src, $node->nid, $node->type, $node->language)) { 298 $node->path = $alias; 299 } 300 } 301 break; 302 case 'delete': 303 path_set_alias('node/'. $node->nid); 304 path_set_alias('node/'. $node->nid .'/feed'); 305 break; 306 } 307 } 308 309 /** 310 * Implements hook_node_type(). 311 */ 312 function pathauto_node_type($op, $info) { 313 switch ($op) { 314 case 'update': 315 if (!empty($info->old_type) && $info->old_type != $info->type) { 316 pathauto_field_attach_rename_bundle('node', $info->old_type, $info->type); 317 } 318 break; 319 case 'delete': 320 pathauto_field_attach_delete_bundle('node', $info->type); 321 break; 322 } 323 } 324 325 /** 326 * Implements hook_form_alter(). 327 * 328 * This allows alias creators to override Pathauto and specify their 329 * own aliases (Pathauto will be invisible to other users). Inserted 330 * into the path module's fieldset in the node form. 331 */ 332 function pathauto_form_alter(&$form, $form_state, $form_id) { 333 // Process only node forms. 334 if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) { 335 $node = $form['#node']; 336 $pattern = FALSE; 337 338 // Find if there is an automatic alias pattern for this node type. 339 if (isset($form['language'])) { 340 $language = isset($form['language']['#value']) ? $form['language']['#value'] : $form['language']['#default_value']; 341 $pattern = trim(variable_get('pathauto_node_'. $form['type']['#value'] .'_'. $language .'_pattern', '')); 342 } 343 if (!$pattern) { 344 $pattern = trim(variable_get('pathauto_node_'. $form['type']['#value'] .'_pattern', '')); 345 if (!$pattern) { 346 $pattern = trim(variable_get('pathauto_node_pattern', '')); 347 } 348 } 349 350 // If there is a pattern, show the automatic alias checkbox. 351 if ($pattern) { 352 if (!isset($node->pathauto_perform_alias)) { 353 if (!empty($node->nid)) { 354 // If this is not a new node, compare it's current alias to the 355 // alias that would be genereted by pathauto. If they are the same, 356 // then keep the automatic alias enabled. 357 _pathauto_include(); 358 $placeholders = pathauto_get_placeholders('node', $node); 359 $pathauto_alias = pathauto_create_alias('node', 'return', $placeholders, "node/{$node->nid}", $node->nid, $node->type, $node->language); 360 $node->pathauto_perform_alias = isset($node->path) && $node->path == $pathauto_alias; 361 } 362 else { 363 // If this is a new node, enable the automatic alias. 364 $node->pathauto_perform_alias = TRUE; 365 } 366 } 367 368 // Add JavaScript that will disable the path textfield when the automatic 369 // alias checkbox is checked. 370 drupal_add_js(drupal_get_path('module', 'pathauto') .'/pathauto.js'); 371 372 // Override path.module's vertical tabs summary. 373 $form['path']['#attached']['js']['vertical-tabs'] = drupal_get_path('module', 'pathauto') . '/pathauto.js'; 374 375 $form['path']['pathauto_perform_alias'] = array( 376 '#type' => 'checkbox', 377 '#title' => t('Automatic alias'), 378 '#default_value' => $node->pathauto_perform_alias, 379 '#description' => t('An alias will be generated for you. If you wish to create your own alias below, uncheck this option.'), 380 '#weight' => -1, 381 ); 382 383 if (user_access('administer pathauto')) { 384 $form['path']['pathauto_perform_alias']['#description'] .= ' '. t('To control the format of the generated aliases, see the <a href="@pathauto">automated alias settings</a>.', array('@pathauto' => url('admin/build/path/pathauto'))); 385 } 386 387 if ($node->pathauto_perform_alias && !empty($node->old_alias) && empty($node->path)) { 388 $form['path']['path']['#default_value'] = $node->old_alias; 389 $node->path = $node->old_alias; 390 } 391 392 // For Pathauto to remember the old alias and prevent the Path-module from deleteing it when Pathauto wants to preserve it 393 if (isset($node->path)) { 394 $form['path']['old_alias'] = array( 395 '#type' => 'value', 396 '#value' => $node->path, 397 ); 398 } 399 } 400 } 401 } 402 403 /** 404 * Implements hook_node_operations(). 405 */ 406 function pathauto_node_operations() { 407 $operations['pathauto_update_alias'] = array( 408 'label' => t('Update URL alias'), 409 'callback' => 'pathauto_node_update_alias_multiple', 410 'callback arguments' => array('bulkupdate', TRUE), 411 ); 412 return $operations; 413 } 414 415 /** 416 * Update the URL aliases for an individual node. 417 * 418 * @param $node 419 * A node object. 420 * @param $op 421 * Operation being performed on the node ('insert', 'update' or 'bulkupdate'). 422 */ 423 function pathauto_node_update_alias($node, $op) { 424 module_load_include('inc', 'pathauto'); 425 $placeholders = pathauto_get_placeholders('node', $node); 426 if ($alias = pathauto_create_alias('node', $op, $placeholders, "node/{$node->nid}", $node->nid, $node->type, $node->language)) { 427 $node->path = $alias; 428 } 429 } 430 431 /** 432 * Update the URL aliases for multiple nodes. 433 * 434 * @param $nids 435 * An array of node IDs. 436 * @param $op 437 * Operation being performed on the nodes ('insert', 'update' or 438 * 'bulkupdate'). 439 * @param $message 440 * A boolean if TRUE will display a message about how many nodes were 441 * updated. 442 */ 443 function pathauto_node_update_alias_multiple($nids, $op, $message = FALSE) { 444 foreach ($nids as $nid) { 445 if ($node = node_load($nid, NULL, TRUE)) { 446 pathauto_node_update_alias($node, $op); 447 } 448 } 449 if ($message) { 450 drupal_set_message(format_plural(count($nids), 'Updated URL alias for 1 node.', 'Updated URL aliases for @count nodes.')); 451 } 452 } 453 454 /** 455 * Wrapper function backwards compatibility. Should be avoided. 456 * 457 * @param $nodes 458 * An array of node IDs. 459 * 460 * @see pathauto_node_update_alias_multiple(). 461 */ 462 function pathauto_node_operations_update($nodes) { 463 return pathauto_node_update_alias_multiple($nodes, 'bulkupdate'); 464 } 465 466 //============================================================================== 467 // Taxonomy related functions. 468 469 /** 470 * Implements hook_taxonomy(). 471 */ 472 function pathauto_taxonomy($op, $type, $object = NULL) { 473 switch ($type) { 474 case 'term': 475 switch ($op) { 476 case 'insert': 477 case 'update': 478 $term = (object) $object; 479 480 // Skip processing if the user has disabled pathauto for the term. 481 if (isset($term->pathauto_perform_alias) && empty($term->pathauto_perform_alias)) { 482 return; 483 } 484 485 // Clear the taxonomy term's static cache. 486 if ($op == 'update') { 487 taxonomy_get_term($term->tid, TRUE); 488 } 489 490 // Use the category info to automatically create an alias 491 _pathauto_include(); 492 if ($term->name) { 493 $count = _taxonomy_pathauto_alias($term, $op); 494 } 495 496 // For all children generate new alias (important if [catpath] used) 497 foreach (taxonomy_get_tree($term->vid, $term->tid) as $subcategory) { 498 $count = _taxonomy_pathauto_alias($subcategory, $op); 499 } 500 501 break; 502 503 case 'delete': 504 // If the category is deleted, remove the path aliases 505 $term = (object) $object; 506 path_set_alias('taxonomy/term/'. $term->tid); 507 path_set_alias(taxonomy_term_path($term)); 508 path_set_alias('forum/'. $term->tid); 509 path_set_alias('taxonomy/term/'. $term->tid .'/0/feed'); 510 break; 511 } 512 break; 513 514 case 'vocabulary': 515 $vocabulary = (object) $object; 516 switch ($op) { 517 case 'delete': 518 pathauto_field_attach_delete_bundle('taxonomy', $vocabulary->vid); 519 break; 520 } 521 break; 522 } 523 } 524 525 //============================================================================== 526 // User related functions. 527 528 /** 529 * Implements hook_user(). 530 */ 531 function pathauto_user($op, &$edit, &$user, $category = NULL) { 532 switch ($op) { 533 case 'insert': 534 case 'update': 535 // Build the user object. 536 $pathauto_user = (object) array_merge((array) $user, $edit); 537 538 // Skip processing if the user has disabled pathauto for the account. 539 if (isset($pathauto_user->pathauto_perform_alias) && empty($pathauto_user->pathauto_perform_alias)) { 540 return; 541 } 542 543 // Use the username to automatically create an alias 544 _pathauto_include(); 545 if ($user->name) { 546 $placeholders = pathauto_get_placeholders('user', $pathauto_user); 547 $src = 'user/'. $user->uid; 548 $alias = pathauto_create_alias('user', $op, $placeholders, $src, $user->uid); 549 550 if (module_exists('blog')) { 551 $new_user = drupal_clone($user); 552 if ($category == 'account') { 553 $new_user->roles = isset($edit['roles']) ? $edit['roles'] : array(); 554 $new_user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; // Add this back 555 } 556 if (node_access('create', 'blog', $new_user)) { 557 $src = 'blog/'. $user->uid; 558 $alias = pathauto_create_alias('blog', $op, $placeholders, $src, $user->uid); 559 } 560 else { 561 path_set_alias('blog/'. $user->uid); 562 path_set_alias('blog/'. $user->uid .'/feed'); 563 } 564 } 565 if (module_exists('tracker')) { 566 $src = 'user/'. $user->uid .'/track'; 567 $alias = pathauto_create_alias('tracker', $op, $placeholders, $src, $user->uid); 568 } 569 if (module_exists('contact')) { 570 $src = 'user/'. $user->uid .'/contact'; 571 $alias = pathauto_create_alias('contact', $op, $placeholders, $src, $user->uid); 572 } 573 } 574 break; 575 case 'delete': 576 // If the user is deleted, remove the path aliases 577 $user = (object) $user; 578 path_set_alias('user/'. $user->uid); 579 580 // They may have enabled these modules and/or feeds when the user was created, so let's try to delete all of them 581 path_set_alias('blog/'. $user->uid); 582 path_set_alias('blog/'. $user->uid .'/feed'); 583 path_set_alias('user/'. $user->uid .'/track'); 584 path_set_alias('user/'. $user->uid .'/track/feed'); 585 path_set_alias('user/'. $user->uid .'/contact'); 586 break; 587 } 588 } 589 590 /** 591 * Implements hook_user_operations(). 592 */ 593 function pathauto_user_operations() { 594 $operations['pathauto_update_alias'] = array( 595 'label' => t('Update URL alias'), 596 'callback' => 'pathauto_user_update_alias_multiple', 597 'callback arguments' => array('bulkupdate', TRUE), 598 ); 599 return $operations; 600 } 601 602 /** 603 * Update the URL aliases for an individual user account. 604 * 605 * @param $account 606 * A user account object. 607 * @param $op 608 * Operation being performed on the account ('insert', 'update' or 609 * 'bulkupdate'). 610 * 611 * @todo Remove support for any sub-path aliases. 612 */ 613 function pathauto_user_update_alias($account, $op) { 614 module_load_include('inc', 'pathauto'); 615 $placeholders = pathauto_get_placeholders('user', $account); 616 pathauto_create_alias('user', $op, $placeholders, "user/{$account->uid}", $account->uid); 617 618 if (module_exists('blog')) { 619 if (node_access('create', 'blog', $account)) { 620 pathauto_create_alias('blog', $op, $placeholders, "blog/{$account->uid}", $account->uid); 621 } 622 else { 623 path_set_alias('blog/'. $user->uid); 624 path_set_alias('blog/'. $user->uid .'/feed'); 625 } 626 } 627 if (module_exists('tracker')) { 628 $alias = pathauto_create_alias('tracker', $op, $placeholders, "user/{$account->uid}/track", $user->uid); 629 } 630 if (module_exists('contact')) { 631 $alias = pathauto_create_alias('contact', $op, $placeholders, "user/{$account->uid}/contact", $user->uid); 632 } 633 } 634 635 /** 636 * Update the URL aliases for multiple user accounts. 637 * 638 * @param $uids 639 * An array of user account IDs. 640 * @param $op 641 * Operation being performed on the accounts ('insert', 'update' or 642 * 'bulkupdate'). 643 * @param $message 644 * A boolean if TRUE will display a message about how many accounts were 645 * updated. 646 */ 647 function pathauto_user_update_alias_multiple($uids, $op, $message = FALSE) { 648 foreach ($uids as $uid) { 649 if ($account = user_load($uid)) { 650 pathauto_user_update_alias($account, $op); 651 } 652 } 653 if ($message) { 654 drupal_set_message(format_plural(count($uids), 'Updated URL alias for 1 user account.', 'Updated URL aliases for @count user accounts.')); 655 } 656 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Jul 9 18:01:44 2012 | Cross-referenced by PHPXref 0.7 |