source: sipei/modules/cck/content.install

drupal-6.x
Last change on this file was ffa4103, checked in by Luis Peña <lpena@…>, 12 años ago

Cambiando el nombre de modulos a modules

  • Propiedad mode establecida a 100755
File size: 23.7 KB
Línea 
1<?php
2// $Id: content.install,v 1.85.2.33 2009/07/14 22:17:05 yched Exp $
3
4function content_requirements($phase) {
5  $requirements = array();
6  // Ensure translations don't break at install time
7  $t = get_t();
8  if (module_exists('views') && (!function_exists('views_api_version') || views_api_version() < 2.0)) {
9    $requirements['cck_views'] = array(
10      'title' => $t('CCK - No Views integration'),
11      'description' => $t("CCK integration with Views module requires Views 6.x-2.0-rc2 or greater."),
12      'severity' => REQUIREMENT_ERROR,
13    );
14  }
15  return $requirements;
16}
17
18/**
19 * 'Safe' version of content_types() to use in updates and installs.
20 *
21 * Can't safely use content_fields() or content_types() in an update to get
22 * a fields array, especially without knowing what field modules are enabled,
23 * or the current state of the database and cache, so create a fields array
24 * from database info that is limited to fields from modules that are
25 * currently enabled.
26 */
27function content_types_install() {
28  drupal_load('module', 'content');
29  module_load_include('inc', 'content', '/includes/content.crud');
30  $module_field_types = $module_widgets = array();
31  foreach (module_list() as $module) {
32    if ($field_type = module_invoke($module, 'field_info')) {
33      $module_field_types[$module] = $field_type;
34    }
35    if ($widget_type = module_invoke($module, 'widget_info')) {
36      $module_widgets[$module] = $widget_type;
37    }
38  }
39  $fields = array();
40  $db_result = db_query("SELECT * FROM {". content_instance_tablename() ."} nfi ".
41    " LEFT JOIN {". content_field_tablename() ."} nf ON nf.field_name = nfi.field_name");
42  while ($row = db_fetch_array($db_result)) {
43    $field = array_merge($row, unserialize($row['global_settings']));
44    unset($field['global_settings']);
45
46    // There may be module data available for currently disabled modules,
47    // or missing module data for currently enabled modules, so start over
48    // to get only field info for enabled modules.
49    unset($field['module']);
50    unset($field['widget_module']);
51    // 'columns' is a reserved word in MySQL4, so our column is named 'db_columns'.
52    $field['columns'] = isset($field['db_columns']) ? $field['db_columns'] : array();
53    unset($field['db_columns']);
54
55    foreach ($module_field_types as $module => $types) {
56      foreach ($types as $type_name => $type) {
57        if ($field['type'] == $type_name) {
58          $field['module'] = $module;
59        }
60      }
61    }
62    foreach ($module_widgets as $module => $types) {
63      foreach ($types as $type_name => $type) {
64        if ($field['widget_type'] == $type_name) {
65          $field['widget_module'] = $module;
66        }
67      }
68    }
69    if (!empty($field['module']) && !empty($field['widget_module'])) {
70      $field['widget_settings'] = unserialize($field['widget_settings']);
71      $field['display_settings'] = unserialize($field['display_settings']);
72      $field['columns'] = (array) module_invoke($field['module'], 'field_settings', 'database columns', $field);
73      $field = content_field_instance_expand($field);
74      $fields[$field['type_name']][$field['field_name']] = $field;
75    }
76  }
77  return $fields;
78}
79
80/**
81 * Implementation of hook_install().
82 */
83function content_install() {
84  variable_set('content_schema_version', 6009);
85  drupal_install_schema('content');
86}
87
88
89/**
90 * Implementation of hook_uninstall().
91 */
92function content_uninstall() {
93  drupal_uninstall_schema('content');
94  // The variable is used during the uninstall process,
95  // so we removed it at the very end.
96  variable_del('content_schema_version');
97  // Remove extra weights.
98  foreach (node_get_types('names') as $type_name) {
99    variable_del("content_extra_weights_$type_name");
100  }
101}
102
103/**
104 * Implementation of hook_enable().
105 */
106function content_enable() {
107  // Make sure old data is emptied out of the caches, since it
108  // may no longer be valid since the module was last enabled,
109  // especially if not all the same field modules are enabled
110  // as before. Especially needed during updates.
111  cache_clear_all('*', 'cache_content', TRUE);
112  content_clear_type_cache(TRUE);
113}
114
115/**
116 * Implementation of hook_disable().
117 */
118function content_disable() {
119  // Make sure old data is emptied out of the caches, since it
120  // may no longer be valid when the module is re-enabled.
121  cache_clear_all('*', 'cache_content', TRUE);
122  content_clear_type_cache(TRUE);
123}
124
125/**
126 * Implementation of hook_schema.
127 */
128function content_schema() {
129
130  // Static (meta) tables.
131
132  $schema['content_node_field'] = array(
133    'fields' => array(
134      'field_name'      => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
135      'type'            => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''),
136      'global_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
137      'required'        => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
138      'multiple'        => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
139      'db_storage'      => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 1),
140      'module'          => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''),
141      'db_columns'      => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
142      'active'          => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
143      'locked'          => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
144    ),
145    'primary key' => array('field_name'),
146  );
147  $schema['content_node_field_instance'] = array(
148    'fields' => array(
149      'field_name'       => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
150      'type_name'        => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
151      'weight'           => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
152      'label'            => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
153      'widget_type'      => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
154      'widget_settings'  => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
155      'display_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE),
156      'description'      => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE),
157      'widget_module'    => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''),
158      'widget_active'    => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
159    ),
160    'primary key' => array('field_name', 'type_name'),
161  );
162  $schema['cache_content'] = drupal_get_schema_unprocessed('system', 'cache');
163
164  // When the module is first installed, the remaining code in the schema
165  // will create errors, since these tables have not yet been created.
166  // We don't need to create data tables on initial installation anyway
167  // since no fields have been created yet, so just return with this much
168  // of the schema.
169
170  if (!db_table_exists('content_node_field') || !db_table_exists('content_node_field_instance')) {
171    return $schema;
172  }
173
174  // Dynamic (data) tables.
175
176  drupal_load('module', 'content');
177
178  // We can't use many helper functions here, like content_fields() or
179  // content_types() or we risk creating a fatal loop from circular
180  // logic when they call other functions that use this schema, so create
181  // the schema directly from a fresh query of the database.
182
183  // content_table_schema() and content_database_info() have no
184  // circular logic and are safe to use here.
185
186  $db_result = db_query("SELECT * FROM {". content_instance_tablename() ."} nfi ".
187    " LEFT JOIN {". content_field_tablename() ."} nf ON nf.field_name = nfi.field_name WHERE nf.active = 1 AND nfi.widget_active = 1");
188  while ($field = db_fetch_array($db_result)) {
189    // 'columns' is a reserved word in MySQL4, so our db column is named 'db_columns'.
190    $field['columns'] = unserialize($field['db_columns']);
191    unset($field['db_columns']);
192
193    $content_table = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
194    $field_table = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD);
195
196
197    // We always add a 'per content type' table for each content type that
198    // has fields.
199    if (!isset($schema[$content_table])) {
200      $schema[$content_table] = content_table_schema();
201    }
202
203    $base_schema = content_table_schema($field);
204    if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
205      // Per-field storage: add the 'per field' table if needed.
206      if (!isset($schema[$field_table])) {
207        $schema[$field_table] = $base_schema;
208      }
209    }
210    else {
211      // Per-type storage: merge the information for the field
212      // in the existing table.
213      $schema[$content_table]['fields'] = array_merge($schema[$content_table]['fields'], $base_schema['fields']);
214      $schema[$content_table]['content fields'] = array_merge($schema[$content_table]['content fields'], $base_schema['content fields']);
215    }
216  }
217  return $schema;
218}
219
220function content_update_last_removed() {
221  return 1008;
222}
223
224/**
225 * Helper function for module updates :
226 * - checks no updates are pending for content.module
227 * - checks content module and the module being updated are both enabled.
228 *
229 * @param $module
230 *   The name of the module being updated.
231 */
232function content_check_update($module = NULL) {
233  $ret = array();
234  // Check that modules are enabled before running their updates.
235  if (!module_exists('content') || ($module && !module_exists($module))) {
236    drupal_set_message(t("Updates for CCK-related modules are not run until the modules are enabled on the <a href=\"@admin-modules-path\">administer modules page</a>. When you enable them, you'll need to return to <a href=\"@update-php\">update.php</a> and run the remaining updates.", array('@admin-modules-path' => url('admin/build/modules'), '@update-php' => base_path() .'update.php?op=selection')), 'warning', FALSE);
237    // The content module is not enabled, nothing else can happen.
238    if ($module && !module_exists('content') && module_exists($module)) {
239      $query_message = t('!module.module has updates but cannot be updated because content.module is not enabled.<br />If and when content.module is enabled, you will need to re-run the update script. You will continue to see this message until the module is enabled and updates are run.', array('!module' => $module));
240    }
241    // The requested module is not enabled, which may be intentional.
242    // Just let the user know there are updates to be processed if enabled later.
243    else {
244      $query_message = t('!module.module has updates and is available in the modules folder but is not enabled.<br />If and when it is enabled, you will need to re-run the update script. You will continue to see this message until the module is enabled and updates are run.', array('!module' => $module ? $module : 'content'));
245    }
246    $ret['#abort'] = array('success' => FALSE, 'query' => $query_message);
247    return $ret;
248  }
249  // Check that content.module is up-to-date before running field module updates.
250  if ($module && (drupal_get_installed_schema_version('content', TRUE) < max(drupal_get_schema_versions('content')))) {
251    drupal_set_message(t('Some updates are still pending. Please return to <a href="@update-php">update.php</a> and run the remaining updates.', array('@update-php' => base_path() .'update.php?op=selection')), 'warning', FALSE);
252    $ret['#abort'] = array('success' => FALSE, 'query' => t('Some updates are still pending.<br/>Please re-run the update script.'));
253    return $ret;
254  }
255  // If everything is OK and updates are not aborted, make sure
256  // content_associate_fields() gets run. With all the complexity of
257  // the dependent updates, it can get missed when an update is aborted.
258  // It won't hurt anything to do this more than once in order to be sure
259  // it doesn't get skipped. Without this step, we can end up with
260  // field modules that are enabled and updated, but not marked as active
261  // in the content_node_field table.
262  if ($module and module_exists($module)) {
263    content_associate_fields($module);
264  }
265}
266
267/**
268 * Add module name to fields table to make it easier to identify the fields to delete when a module
269 * is uninstalled.
270 *
271 * Needed because the value drops out of content_info() when module is disabled, so there
272 * is no other way to find the associated fields.
273 */
274function content_update_6000() {
275  if ($abort = content_check_update()) {
276    return $abort;
277  }
278
279  $ret = array();
280
281  drupal_load('module', 'content');
282  if (db_column_exists(content_field_tablename(), 'active')) {
283    return $ret;
284  }
285  db_add_field($ret, content_field_tablename(), 'module', array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''));
286  db_add_field($ret, content_field_tablename(), 'db_columns', array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'initial' => ''));
287  db_add_field($ret, content_field_tablename(), 'active', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
288  db_add_field($ret, content_instance_tablename(), 'widget_module', array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''));
289  db_add_field($ret, content_instance_tablename(), 'widget_active', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
290
291  // This will update the table for any modules enabled at this time.
292  foreach (module_list() as $module) {
293    content_associate_fields($module);
294  }
295
296  // Fix the cache_content schema
297  if (db_table_exists('cache_content')) {
298    db_drop_table($ret, 'cache_content');
299  }
300  db_create_table($ret, 'cache_content', drupal_get_schema_unprocessed('system', 'cache'));
301  variable_set('content_schema_version', 6000);
302
303  // The cache table had to be used to store data until this update ran,
304  // so clear cache out now that we're switching back to the cache_content table.
305  $ret[] = update_sql('DELETE FROM {cache}');
306
307  return $ret;
308}
309
310/**
311 * Rename node_field and node_field_instance tables.
312 *
313 * This is a carryover from when the data tables were renamed,
314 * postponed so we wouldn't create any more havoc than necessary
315 * until a major version change.
316 *
317 * Using 'content_node_field' instead of 'content_field'
318 * to avoid conflicts with field tables that will be prefixed
319 * with 'content_field'.
320 */
321function content_update_6001() {
322  if ($abort = content_check_update()) {
323    return $abort;
324  }
325
326  $ret = array();
327  drupal_load('module', 'content');
328  if (db_table_exists('content_node_field')) {
329    return $ret;
330  }
331  db_rename_table($ret, 'node_field', 'content_node_field');
332  db_rename_table($ret, 'node_field_instance', 'content_node_field_instance');
333  variable_set('content_schema_version', 6001);
334  content_clear_type_cache(TRUE);
335  return $ret;
336}
337
338/**
339 * Get rid of automatic per content tables for content types that have no fields.
340 * Switching to adding those tables only when needed.
341 */
342function content_update_6002() {
343  if ($abort = content_check_update()) {
344    return $abort;
345  }
346
347  $ret = array();
348
349  drupal_load('module', 'content');
350  $db_types = content_types_install();
351  $field_types = array();
352
353  $result = db_query("SELECT DISTINCT type_name FROM {". content_instance_tablename() ."}");
354  while ($type = db_fetch_array($result)) {
355    $field_types[] = $type['type_name'];
356  }
357
358  foreach ($db_types as $content_type => $content_info) {
359    if (!in_array($content_type, $field_types)) {
360      $table = _content_tablename($content_type, CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
361      if (db_table_exists($table)) {
362        db_drop_table($ret, $table);
363      }
364    }
365  }
366  variable_set('content_schema_version', 6002);
367  content_clear_type_cache(TRUE);
368  return $ret;
369}
370
371/**
372 * 'db_columns' column 1st got introduced as 'columns', which is forbidden in MySQL 4.
373 * This update function will only be useful for early D6 testers...
374 */
375function content_update_6003() {
376  if ($abort = content_check_update()) {
377    return $abort;
378  }
379
380  $ret = array();
381  if (db_column_exists('content_node_field', 'columns')) {
382    db_change_field($ret, 'content_node_field', 'columns', 'db_columns', array('type' => 'text', 'size' => 'medium', 'not null' => TRUE));
383  }
384  variable_set('content_schema_version', 6003);
385  return $ret;
386}
387
388/**
389 * Index the 'nid' column on data tables to optimize node deletion.
390 * Large tables might deserve a multipass update.
391 */
392function content_update_6004(&$sandbox) {
393  if ($abort = content_check_update()) {
394    return $abort;
395  }
396
397  $ret = array();
398
399  // Do nothing if the indexes were already created by D5's content_update_1009.
400  if (variable_get('content_update_1009', FALSE)) {
401    return $ret;
402  }
403
404  // Gather list of tables.
405  if (!isset($sandbox['tables'])) {
406    drupal_load('module', 'content');
407    $sandbox['tables'] = array();
408    $result = db_query('SELECT * FROM {'. content_instance_tablename() .'} nfi '.
409      ' LEFT JOIN {'. content_field_tablename() .'} nf ON nf.field_name = nfi.field_name');
410    while ($field = db_fetch_array($result)) {
411      if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
412        $table = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD);
413      }
414      else {
415        $table = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
416      }
417      $sandbox['tables'][$table] = $table;
418    }
419    $sandbox['count'] = count($sandbox['tables']);
420  }
421
422  // One pass : add index on one table.
423  if ($table = array_shift($sandbox['tables'])) {
424    db_add_index($ret, $table, 'nid', array('nid'));
425  }
426
427  if ($sandbox['count']) {
428    $ret['#finished'] = 1 - count($sandbox['tables']) / $sandbox['count'];
429  }
430  variable_set('content_schema_version', 6004);
431  return $ret;
432}
433
434/**
435 * Add 'locked' property for fields.
436 */
437function content_update_6005() {
438  if ($abort = content_check_update()) {
439    return $abort;
440  }
441
442  $ret = array();
443  drupal_load('module', 'content');
444  db_add_field($ret, content_field_tablename(), 'locked', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
445  variable_set('content_schema_version', 6005);
446  return $ret;
447}
448
449/**
450 * Make sure the 'locked' column is NOT NULL (error in previous content_update_6005().
451 */
452function content_update_6006() {
453  if ($abort = content_check_update()) {
454    return $abort;
455  }
456
457  $ret = array();
458  drupal_load('module', 'content');
459  db_change_field($ret, content_field_tablename(), 'locked', 'locked', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0));
460  variable_set('content_schema_version', 6006);
461  return $ret;
462}
463
464/**
465 * Dummy update function to make sure the theme registry and css / JS aggregated files
466 * are updated.
467 */
468function content_update_6007() {
469  if ($abort = content_check_update()) {
470    return $abort;
471  }
472
473  variable_set('content_schema_version', 6007);
474  return array();
475}
476
477/**
478 * Dummy update function to make sure schema version gets updated.
479 */
480function content_update_6008() {
481  if ($abort = content_check_update()) {
482    return $abort;
483  }
484
485  variable_set('content_schema_version', 6008);
486  return array();
487}
488
489/**
490 * Add the 'exclude from $content' display setting to all existing field instances.
491 */
492function content_update_6009() {
493  if ($abort = content_check_update()) {
494    return $abort;
495  }
496
497  $ret = array();
498  $result = db_query("SELECT *  FROM {content_node_field_instance}");
499  while ($type = db_fetch_array($result)) {
500    $new_settings = array();
501    $display_settings = unserialize($type['display_settings']);
502    if (!empty($display_settings)) {
503      foreach ($display_settings as $key => $val) {
504        $new_settings[$key] = $val;
505        if ($key !== 'label' && is_array($val)) {
506          $new_settings[$key]['exclude'] = 0;
507        }
508      }
509    }
510    else {
511      $new_settings = array(
512        'label' => array('format' => 'above'),
513        'full' => array('format' => 'default', 'exclude' => 0),
514        'teaser' => array('format' => 'default', 'exclude' => 0),
515        );
516    }
517    db_query("UPDATE {content_node_field_instance} SET display_settings='%s' WHERE field_name='%s' AND type_name='%s'", serialize($new_settings), $type['field_name'], $type['type_name']);
518  }
519  variable_set('content_schema_version', 6009);
520  return $ret;
521}
522
523/**
524 * Fix multiple serialization caused by per-field to per-type migration.
525 * See http://drupal.org/node/407446.
526 */
527function content_update_6010(&$sandbox) {
528  if ($abort = content_check_update()) {
529    return $abort;
530  }
531  $ret = array();
532
533  drupal_load('module', 'content');
534
535  // Gather list of tables and columns that need to be updated.
536  if (!isset($sandbox['tables'])) {
537    $sandbox['tables'] = array();
538    $fields = content_fields();
539    foreach ($fields as $name => $field) {
540      $db_info = content_database_info($field);
541      foreach ($db_info['columns'] as $column => $attributes) {
542        if (isset($attributes['serialize']) && $attributes['serialize']) {
543          $sandbox['tables'][$db_info['table']]['table'] = $db_info['table'];
544          $sandbox['tables'][$db_info['table']]['columns'][] = $attributes['column'];
545          $sandbox['tables'][$db_info['table']]['multiple'] = $field['multiple'];
546        }
547      }
548    }
549    $sandbox['count'] = count($sandbox['tables']);
550    $sandbox['current_vid'] = 0;
551    $sandbox['current_delta'] = 0;
552  }
553
554  // Number of rows to fix in one pass.
555  $limit = 500;
556  // Start correcting data.
557  if ($table_info = array_shift($sandbox['tables'])) {
558    $table = $table_info['table'];
559    $columns = $table_info['columns'];
560
561    if ($table_info['multiple']) {
562      $query = "SELECT * FROM {" . $table . "} WHERE (vid = %d AND delta > %d) OR (vid > %d) ORDER BY vid ASC, delta ASC";
563      $args = array($sandbox['current_vid'], $sandbox['current_delta'], $sandbox['current_vid']);
564    }
565    else {
566      $query = "SELECT * FROM {" . $table . "} WHERE vid > %d ORDER BY vid ASC";
567      $args = array($sandbox['current_vid']);
568    }
569    $result = db_query_range($query, $args, 0, $limit);
570    $count = 0;
571    while ($row = db_fetch_array($result)) {
572      $update_query = $update_args = array();
573      foreach ($columns as $column) {
574        $data = $row[$column];
575        // No need to do anything if the data is NULL.
576        if (!empty($data)) {
577          // Unserialize until we get something that is not a string
578          while (is_string($data)) {
579            $unserialized = @unserialize($data);
580            if ($unserialized !== FALSE) {
581              $data = $unserialized;
582            }
583            else {
584              // TODO : test with a serialized string, just in case...
585              break;
586            }
587          }
588          // Re-serialize once.
589          $data = serialize($data);
590          // If we end up with something different than what we started with, update.
591          if ($data !== $row[$column]) {
592            $update_query[] = "$column = '%s'";
593            $update_args[] = $data;
594          }
595        }
596      }
597      if ($update_query) {
598        $update_args[] = $row['vid'];
599        db_query("UPDATE {" . $table . "} SET ". implode(', ', $update_query) ." WHERE vid = %d", $update_args);
600      }
601      $sandbox['current_vid'] = $row['vid'];
602      $sandbox['current_delta'] = isset($row['delta']) ? $row['delta'] : 0;
603      $count++;
604    }
605    if ($count == $limit) {
606      // Add the table back into the list of tables to be processed if rows remain.
607      array_unshift($sandbox['tables'], $table_info);
608    }
609    else {
610      // Done with this table: reset vid and delta markers.
611      $sandbox['current_vid'] = 0;
612      $sandbox['current_delta'] = 0;
613      $ret[] = array('success' => TRUE, 'query' => "Fixed serialized values in table $table");
614    }
615  }
616
617  if ($sandbox['count']) {
618    $ret['#finished'] = 1 - count($sandbox['tables']) / $sandbox['count'];
619  }
620  return $ret;
621}
Nota: Vea TracBrowser para ayuda de uso del navegador del repositorio.