? _patches ? _scripts ? files ? modules/devel.module ? sites/morbus.totalnetnh.net Index: database/database.mysql =================================================================== RCS file: /cvs/drupal/drupal/database/database.mysql,v retrieving revision 1.174 diff -u -r1.174 database.mysql --- database/database.mysql 23 Mar 2005 20:36:41 -0000 1.174 +++ database/database.mysql 6 Apr 2005 17:50:45 -0000 @@ -739,6 +739,7 @@ hierarchy tinyint(3) unsigned NOT NULL default '0', multiple tinyint(3) unsigned NOT NULL default '0', required tinyint(3) unsigned NOT NULL default '0', + tags tinyint(3) unsigned NOT NULL default '0', module varchar(255) NOT NULL default '', weight tinyint(4) NOT NULL default '0', PRIMARY KEY (vid) Index: database/database.pgsql =================================================================== RCS file: /cvs/drupal/drupal/database/database.pgsql,v retrieving revision 1.110 diff -u -r1.110 database.pgsql --- database/database.pgsql 3 Apr 2005 08:56:48 -0000 1.110 +++ database/database.pgsql 6 Apr 2005 17:50:45 -0000 @@ -738,6 +738,7 @@ hierarchy smallint NOT NULL default '0', multiple smallint NOT NULL default '0', required smallint NOT NULL default '0', + tags smallint NOT NULL default '0', module varchar(255) NOT NULL default '', weight smallint NOT NULL default '0', PRIMARY KEY (vid) Index: database/updates.inc =================================================================== RCS file: /cvs/drupal/drupal/database/updates.inc,v retrieving revision 1.100 diff -u -r1.100 updates.inc --- database/updates.inc 23 Mar 2005 06:12:30 -0000 1.100 +++ database/updates.inc 6 Apr 2005 17:50:45 -0000 @@ -104,7 +104,8 @@ "2005-02-23" => "update_125", "2005-03-03" => "update_126", "2005-03-18" => "update_127", - "2005-03-21" => "update_128" + "2005-03-21" => "update_128", + "2005-03-30" => "update_129" ); function update_32() { @@ -2350,4 +2351,17 @@ return $ret; } +function update_129() { + $ret = array(); + + if ($GLOBALS['db_type'] == 'mysql') { + $ret[] = update_sql("ALTER TABLE {vocabulary} ADD tags tinyint(3) unsigned default '0' NOT NULL"); + } + elseif ($GLOBALS['db_type'] == 'pgsql') { + $ret[] = update_sql("ALTER TABLE {vocabulary} ADD tags smallint default '0' NOT NULL"); + } + + return $ret; +} + ?> Index: misc/drupal.css =================================================================== RCS file: /cvs/drupal/drupal/misc/drupal.css,v retrieving revision 1.100 diff -u -r1.100 drupal.css --- misc/drupal.css 27 Mar 2005 21:28:25 -0000 1.100 +++ misc/drupal.css 6 Apr 2005 17:50:46 -0000 @@ -367,9 +367,10 @@ #permissions td.module, #blocks td.region { font-weight: bold; } -#permissions td.permission, #blocks td.block { - padding-left: 2em; +#permissions td.permission, #blocks td.block, #taxonomy_admin td.term, #taxonomy_admin td.message { + padding-left: 1.5em; } + #access-rules .access-type, #access-rules .rule-type { margin-right: 1em; float: left; Index: modules/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy.module,v retrieving revision 1.191 diff -u -r1.191 taxonomy.module --- modules/taxonomy.module 6 Apr 2005 04:48:39 -0000 1.191 +++ modules/taxonomy.module 6 Apr 2005 17:50:46 -0000 @@ -68,11 +68,6 @@ 'access' => user_access('administer taxonomy'), 'type' => MENU_CALLBACK); - $items[] = array('path' => 'admin/taxonomy/preview/vocabulary', 'title' => t('preview vocabulary'), - 'callback' => 'taxonomy_admin', - 'access' => user_access('administer taxonomy'), - 'type' => MENU_CALLBACK); - $items[] = array('path' => 'admin/taxonomy/add/term', 'title' => t('add term'), 'callback' => 'taxonomy_admin', 'access' => user_access('administer taxonomy'), @@ -88,6 +83,22 @@ 'access' => user_access('access content'), 'type' => MENU_CALLBACK); } + else { + if (is_numeric(arg(2))) { + $items[] = array('path' => 'admin/taxonomy/' . arg(2), 'title' => t('add term'), + 'callback' => 'taxonomy_admin', + 'access' => user_access('administer taxonomy'), + 'type' => MENU_CALLBACK); + + $items[] = array('path' => 'admin/taxonomy/' . arg(2) . '/list', 'title' => t('list'), + 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10); + + $items[] = array('path' => 'admin/taxonomy/' . arg(2) . '/add/term', 'title' => t('add term'), + 'callback' => 'taxonomy_admin', + 'access' => user_access('administer taxonomy'), + 'type' => MENU_LOCAL_TASK); + } + } return $items; } @@ -102,9 +113,10 @@ $form .= form_textarea(t('Description'), 'description', $edit['description'], 60, 5, t('Description of the vocabulary; can be used by modules.')); $form .= form_textfield(t('Help text'), 'help', $edit['help'], 50, 255, t('Instructions to present to the user when choosing a term.')); $form .= form_checkboxes(t('Types'), 'nodes', $edit['nodes'], $nodes, t('A list of node types you want to associate with this vocabulary.'), NULL, TRUE); - $form .= form_checkbox(t('Related terms'), 'relations', 1, $edit['relations'], t('Allows related terms in this vocabulary.', array('%help-url' => url('admin/help/taxonomy', NULL, NULL, 'related-terms')))); $form .= form_radios(t('Hierarchy'), 'hierarchy', $edit['hierarchy'], array(t('Disabled'), t('Single'), t('Multiple')), t('Allows a tree-like hierarchy between terms of this vocabulary.', array('%help-url' => url('admin/help/taxonomy', NULL, NULL, 'hierarchy')))); - $form .= form_checkbox(t('Multiple select'), 'multiple', 1, $edit['multiple'], t('Allows nodes to have more than one term in this vocabulary.')); + $form .= form_checkbox(t('Related terms'), 'relations', 1, $edit['relations'], t('Allows related terms in this vocabulary.', array('%help-url' => url('admin/help/taxonomy', NULL, NULL, 'related-terms')))); + $form .= form_checkbox(t('Free tagging'), 'tags', 1, $edit['tags'], t('Content is categorized by typing terms instead of choosing from a list.')); + $form .= form_checkbox(t('Multiple select'), 'multiple', 1, $edit['multiple'], t('Allows nodes to have more than one term from this vocabulary (always true for free tagging).')); $form .= form_checkbox(t('Required'), 'required', 1, $edit['required'], t('If enabled, every node must have at least one term in this vocabulary.')); $form .= form_weight(t('Weight'), 'weight', $edit['weight'], 10, t('In listings, the heavier vocabularies will sink and the lighter vocabularies will be positioned nearer the top.')); $form .= form_submit(t('Submit')); @@ -122,7 +134,7 @@ $edit['nodes'] = array(); } - $data = array('name' => $edit['name'], 'description' => $edit['description'], 'help' => $edit['help'], 'multiple' => $edit['multiple'], 'required' => $edit['required'], 'hierarchy' => $edit['hierarchy'], 'relations' => $edit['relations'], 'weight' => $edit['weight'], 'module' => isset($edit['module']) ? $edit['module'] : 'taxonomy'); + $data = array('name' => $edit['name'], 'description' => $edit['description'], 'help' => $edit['help'], 'multiple' => $edit['multiple'], 'required' => $edit['required'], 'hierarchy' => $edit['hierarchy'], 'relations' => $edit['relations'], 'tags' => $edit['tags'], 'weight' => $edit['weight'], 'module' => isset($edit['module']) ? $edit['module'] : 'taxonomy'); if ($edit['vid'] && $edit['name']) { db_query('UPDATE {vocabulary} SET '. _taxonomy_prepare_update($data) .' WHERE vid = %d', $edit['vid']); db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d", $edit['vid']); @@ -345,36 +357,77 @@ * Generate a tabular listing of administrative functions for vocabularies. */ function taxonomy_overview() { - $header = array(t('Name'), t('Type'), array('data' => t('Operations'), 'colspan' => '3')); - $vocabularies = taxonomy_get_vocabularies(); + $vid = arg(2); - foreach ($vocabularies as $vocabulary) { - if ($vocabulary->module == 'taxonomy') { // only show vocabularies that can be configured through the vocabulary module - $types = array(); - foreach($vocabulary->nodes as $type) { - $node_type = node_invoke($type, 'node_name'); - $types[] = $node_type ? $node_type : $type; + // all vocab overview. all vocabularies save for those defined + // as "free tagging" have their terms listed on this screen. + if (!$vid) { + $header = array(t('Name'), t('Type'), array('data' => t('Operations'), 'colspan' => '3')); + $vocabularies = taxonomy_get_vocabularies(); + foreach ($vocabularies as $vocabulary) { + if ($vocabulary->module == 'taxonomy') { + $types = array(); + foreach ($vocabulary->nodes as $type) { + $node_type = node_invoke($type, 'node_name'); + $types[] = $node_type ? $node_type : $type; + } + $rows[] = array(''.check_plain($vocabulary->name).'', implode(', ', $types), l(t('edit vocabulary'), "admin/taxonomy/edit/vocabulary/$vocabulary->vid"), l(t('add term'), "admin/taxonomy/add/term/$vocabulary->vid"), l(t('view terms'), "admin/taxonomy/$vocabulary->vid")); + + // show terms if non-free. + if (!$vocabulary->tags) { + $tree = taxonomy_get_tree($vocabulary->vid); + if ($tree) { + foreach ($tree as $term) { + $rows[] = array(array('data' => _taxonomy_depth($term->depth) . ' ' . check_plain($term->name), 'class' => 'term'), NULL, NULL, NULL, l(t('edit term'), "admin/taxonomy/edit/term/$term->tid")); + } + } + else { + $rows[] = array(array('data' => t('No terms available.'), 'colspan' => '5', 'class' => 'message')); + } + } + elseif ($vocabulary->tags) { + $rows[] = array(array('data' => t('This is a free tagging vocabulary:') . ' ' . l(t('view terms'), "admin/taxonomy/$vocabulary->vid") . '.', 'colspan' => '5', 'class' => 'message')); + } } + } + + if (!$rows) { + $rows[] = array(array('data' => t('No categories available.'), 'colspan' => '5', 'class' => 'message')); + } + } - $rows[] = array(check_plain($vocabulary->name), implode(', ', $types), l(t('edit vocabulary'), "admin/taxonomy/edit/vocabulary/$vocabulary->vid"), l(t('add term'), "admin/taxonomy/add/term/$vocabulary->vid"), l(t('preview form'), "admin/taxonomy/preview/vocabulary/$vocabulary->vid")); + // free tagging vocabs get their own terms pager since there + // is a far greater chance of 1000+ terms in these vocabs. + else { + $header = array(t('Name'), array('data' => t('Operations'), 'width' => '60')); + $vocabulary = taxonomy_get_vocabulary($vid); + if ($vocabulary->module == 'taxonomy') { + drupal_set_title("'".check_plain($vocabulary->name)."' vocabulary terms"); + $start_from = $_GET['from'] ? $_GET['from'] : 0; + $total_entries = 0; // total count for pager. + $page_increment = 25; // number of tids per page. + $displayed_count = 0; // number of tids shown. $tree = taxonomy_get_tree($vocabulary->vid); - if ($tree) { - unset($data); foreach ($tree as $term) { - $data .= _taxonomy_depth($term->depth) .' '. check_plain($term->name) .' ('. l(t('edit term'), "admin/taxonomy/edit/term/$term->tid") .')
'; + $total_entries++; // we're counting all-totals, not displayed. + if (($start_from && $start_from > $total_entries) || ($displayed_count == $page_increment)) { continue; } + $rows[] = array(_taxonomy_depth($term->depth) . ' ' . check_plain($term->name), l(t('edit term'), "admin/taxonomy/edit/term/$term->tid")); + $displayed_count++; // we're counting tids displayed. } - $rows[] = array(array('data' => $data, 'colspan' => '5')); + + if (!$rows) { + $rows[] = array(array('data' => t('No terms available.'), 'colspan' => '2')); } - } - } - if (!$rows) { - $rows[] = array(array('data' => t('No categories available.'), 'colspan' => '5')); + $GLOBALS['pager_from_array'][] = $start_from; + $GLOBALS['pager_total'][] = $total_entries; + $rows[] = array(array('data' => theme('pager', NULL, $page_increment), 'colspan' => '2')); + } } - return theme('table', $header, $rows); + return theme('table', $header, $rows, array('id' => 'taxonomy_admin')); } /** @@ -397,10 +450,11 @@ * Generate a set of options for selecting a term from all vocabularies. Can be * passed to form_select. */ -function taxonomy_form_all($value = 0, $help = NULL, $name = 'taxonomy') { +function taxonomy_form_all($free_tags = 0) { $vocabularies = taxonomy_get_vocabularies(); $options = array(); foreach ($vocabularies as $vid => $vocabulary) { + if ($vocabulary->tags && !$free_tags) { continue; } $tree = taxonomy_get_tree($vid); $options[$vocabulary->name] = array(); if ($tree) { @@ -442,21 +496,40 @@ * Generate a form for selecting terms to associate with a node. */ function taxonomy_node_form($type, $node = '', $help = NULL, $name = 'taxonomy') { - if (!$node->taxonomy) { + if (!array_key_exists('taxonomy', $node)) { if ($node->nid) { - $terms = array_keys(taxonomy_node_get_terms($node->nid)); + $terms = taxonomy_node_get_terms($node->nid); } else { - $terms = 0; + $terms = array(); } } else { $terms = $node->taxonomy; } - + $c = db_query("SELECT v.*, n.type FROM {vocabulary} v INNER JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s' ORDER BY v.weight, v.name", $type); while ($vocabulary = db_fetch_object($c)) { - $result[] = taxonomy_form($vocabulary->vid, $terms, $help, $name); + if ($vocabulary->tags) { + $typed_terms = array(); + foreach ($terms as $term) { + if ($term->vid == $vocabulary->vid) { + + // commas and quotes in terms are special cases, so encode 'em. + if (preg_match('/,/', $term->name) || preg_match('/"/', $term->name)) { + $term->name = '"'.preg_replace('/"/', '""', $term->name).'"'; + } + + $typed_terms[] = $term->name; + } + } + $typed_string = implode(', ', $typed_terms) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL); + $result[] = form_textfield($vocabulary->name, 'taxonomy][tags]['. $vocabulary->vid, $typed_string, 50, 100, t('A comma-separated list of terms describing this content (Example: funny, bungie jumping, "Company, Inc.").'), NULL, ($vocabulary->required ? TRUE : FALSE)); + } + else { + $ntterms = array_key_exists('taxonomy', $node) ? $terms : array_keys($terms); + $result[] = taxonomy_form($vocabulary->vid, $ntterms, $help, $name); + } } return $result ? $result : array(); } @@ -490,11 +563,69 @@ } /** + * Make sure incoming vids are free tagging enabled. + */ +function taxonomy_node_validate(&$node) { + if ($node->taxonomy) { + $terms = $node->taxonomy; + if ($terms['tags']) { + foreach ($terms['tags'] as $vid => $vid_value) { + $vocabulary = taxonomy_get_vocabulary($vid); + if (!$vocabulary->tags) { + form_set_error("taxonomy[tags][$vid", t('The %name vocabulary can not be modified in this way.', array('%name' => "$vocabulary->name"))); + } + } + } + } +} + +/** * Save term associations for a given node. */ function taxonomy_node_save($nid, $terms) { taxonomy_node_delete($nid); + // free tagging vocabularies do not send their tids in the form, + // so we'll detect them here and process them independently. + if ($terms['tags']) { + $typed_input = $terms['tags']; + unset($terms['tags']); + + foreach ($typed_input as $vid => $vid_value) { + // this regexp allows the following types of user input: + // this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar + $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x'; + preg_match_all($regexp, $vid_value, $matches); + $typed_terms = $matches[1]; + + foreach ($typed_terms as $typed_term) { + // if a user has escaped a term (to demonstrate that it is a group, + // or includes a comma or quote character), we remove the escape + // formatting so to save the term into the DB as the user intends. + $typed_term = str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $typed_term)); + $typed_term = trim($typed_term); + if ($typed_term == "") { continue; } + + // see if the term exists in the chosen vocabulary + // and return the tid, otherwise, add a new record. + $possibilities = taxonomy_get_term_by_name($typed_term); + $typed_term_tid = NULL; // tid match if any. + foreach ($possibilities as $possibility) { + if ($possibility->vid == $vid) { + $typed_term_tid = $possibility->tid; + } + } + + if (!$typed_term_tid) { + $new_term = taxonomy_save_term(array('vid' => $vid, 'name' => $typed_term)); + $typed_term_tid = $new_term['tid']; + } + + db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $typed_term_tid); + } + } + } + if (is_array($terms)) { foreach ($terms as $term) { if (is_array($term)) { @@ -637,7 +768,7 @@ } } } - + return $tree ? $tree : array(); } @@ -894,6 +1025,9 @@ case 'delete': taxonomy_node_delete($node->nid); break; + case 'validate': + taxonomy_node_validate($node); + break; case 'rss item': return taxonomy_rss_item($node); break; @@ -979,6 +1113,20 @@ if (empty($op)) { $op = arg(2); } + + // special block for our "add term" menu localtask. this covers + // the use of the "add term" LOCAL_TASK on a "view terms" page. + if (is_numeric(arg(2)) && arg(3) == 'add' && arg(4) == 'term') { + if ($op != t('Submit')) { + $output = taxonomy_form_term(array('vid' => arg(2))); + print theme('page', $output); + return; + } + else { + taxonomy_save_term($edit); + drupal_goto('admin/taxonomy'); + } + } switch ($op) { case 'add': @@ -997,9 +1145,6 @@ $output = taxonomy_form_term(object2array(taxonomy_get_term(arg(4)))); } break; - case 'preview': - $output = taxonomy_form(arg(4)); - break; case t('Delete'): if (!$edit['confirm']) { if (arg(3) == 'vocabulary') { @@ -1051,7 +1196,7 @@ case 'admin/modules#description': return t('Enables the categorization of content.'); case 'admin/taxonomy': - return t('

The taxonomy module allows you to classify content into categories and subcategories; it allows multiple lists of categories for classification (controlled vocabularies) and offers the possibility of creating thesauri (controlled vocabularies that indicate the relationship of terms) and taxonomies (controlled vocabularies where relationships are indicated hierarchically). To delete a term choose "edit term". To delete a vocabulary, and all its terms, choose "edit vocabulary".

'); + return t('

The taxonomy module allows you to classify content into categories and subcategories; it allows multiple lists of categories for classification (controlled vocabularies) and offers the possibility of creating thesauri (controlled vocabularies that indicate the relationship of terms), taxonomies (controlled vocabularies where relationships are indicated hierarchically), and free vocabularies where terms, or tags, are defined during content creation. To delete a term, choose "edit term". To delete a vocabulary and all its terms, choose "edit vocabulary".

'); case 'admin/taxonomy/add/vocabulary': return t("

When you create a controlled vocabulary you are creating a set of terms to use for describing content (known as descriptors in indexing lingo). Drupal allows you to describe each piece of content (blog, story, etc.) using one or many of these terms. For simple implementations, you might create a set of categories without subcategories, similar to Slashdot.org's or Kuro5hin.org's sections. For more complex implementations, you might create a hierarchical list of categories.

"); case 'admin/help#taxonomy': @@ -1069,8 +1214,9 @@
  • Vocabulary name: The name for this vocabulary. Example: Dairy.
  • Description: Description of the vocabulary. This can be used by modules and feeds.
  • Types: The list of content types you want to associate this vocabulary with. Some available types are blog, book, forum, page, and story.
  • -
  • Related terms: Allows relationships between terms within this vocabulary. Think of these as see also references.
  • Hierarchy: Allows a tree-like vocabulary, as in our Foods example above.
  • +
  • Related terms: Allows relationships between terms within this vocabulary. Think of these as see also references.
  • +
  • Free tagging: Content is categorized at time of creation by typing comma-separated terms instead of choosing from a pre-defined list. This is helpful if you\'d like to define and expand the vocabulary during content creation, as opposed to deciding all terms beforehand and then choosing from a controlled list. Administrators may also edit and modify the vocabulary through the normal administrative screens.
  • Multiple select: Allows pieces of content to be described using more than one term. Content may then appear on multiple taxonomy pages.
  • Required: If selected, each piece of content must have a term in this vocabulary associated with it.
  • Weight: The overall weight for this vocabulary in listings with multiple vocabularies.