Commit e6a88be5 authored by Gábor Hojtsy's avatar Gábor Hojtsy
Browse files

#193333 by quicksketch et al: taxonomy drag and drop support

parent 9a96837b
......@@ -2012,9 +2012,10 @@ function drupal_get_js($scope = 'header', $javascript = NULL) {
* group. Depth updates the target element with the current indentation.
* @param $relationship
* String describing where the $action variable should be performed. Either
* 'parent', 'sibling', or 'self'. Parent will only look for fields up the
* tree. Sibling will look for fields in the same group in rows above and
* below it. Self affects the dragged row itself.
* 'parent', 'sibling', 'group', or 'self'. Parent will only look for fields
* up the tree. Sibling will look for fields in the same group in rows above
* and below it. Self affects the dragged row itself. Group affects the
* dragged row, plus any children below it (the entire dragged group).
* @param $group
* A class name applied on all related form elements for this action.
* @param $subgroup
......
......@@ -413,7 +413,20 @@ Drupal.tableDrag.prototype.dropRow = function(event) {
var droppedRow = self.rowObject.element;
// The row is already in the right place so we just release it.
if (self.rowObject.changed == true) {
// Update the fields in the dropped row.
self.updateFields(droppedRow);
// If a setting exists for affecting the entire group, update all the
// fields in the entire dragged group.
for (var group in self.tableSettings) {
var rowSettings = self.rowSettings(group, droppedRow);
if (rowSettings.relationship == 'group') {
for (n in self.rowObject.children) {
self.updateField(self.rowObject.children[n], group);
}
}
}
self.rowObject.markChanged();
if (self.changed == false) {
$(Drupal.theme('tableDragChangedWarning')).insertAfter(self.table).hide().fadeIn('slow');
......@@ -562,114 +575,127 @@ Drupal.tableDrag.prototype.updateFields = function(changedRow) {
for (var group in this.tableSettings) {
// Each group may have a different setting for relationship, so we find
// the source rows for each seperately.
var rowSettings = this.rowSettings(group, changedRow);
this.updateField(changedRow, group);
}
}
// Set the row as it's own target.
if (rowSettings.relationship == 'self') {
var sourceRow = changedRow;
}
// Siblings are easy, check previous and next rows.
else if (rowSettings.relationship == 'sibling') {
var previousRow = $(changedRow).prev('tr').get(0);
var nextRow = $(changedRow).next('tr').get(0);
var sourceRow = changedRow;
if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) {
if (this.indentEnabled) {
if ($('.indentations', previousRow).size() == $('.indentations', changedRow)) {
sourceRow = previousRow;
}
}
else {
/**
* After the row is dropped, update a single table field according to specific
* settings.
*
* @param changedRow
* DOM object for the row that was just dropped.
* @param group
* The settings group on which field updates will occur.
*/
Drupal.tableDrag.prototype.updateField = function(changedRow, group) {
var rowSettings = this.rowSettings(group, changedRow);
// Set the row as it's own target.
if (rowSettings.relationship == 'self' || rowSettings.relationship == 'group') {
var sourceRow = changedRow;
}
// Siblings are easy, check previous and next rows.
else if (rowSettings.relationship == 'sibling') {
var previousRow = $(changedRow).prev('tr').get(0);
var nextRow = $(changedRow).next('tr').get(0);
var sourceRow = changedRow;
if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) {
if (this.indentEnabled) {
if ($('.indentations', previousRow).size() == $('.indentations', changedRow)) {
sourceRow = previousRow;
}
}
else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) {
if (this.indentEnabled) {
if ($('.indentations', nextRow).size() == $('.indentations', changedRow)) {
sourceRow = nextRow;
}
}
else {
sourceRow = nextRow;
}
else {
sourceRow = previousRow;
}
}
// Parents, look up the tree until we find a field not in this group.
// Go up as many parents as indentations in the changed row.
else if (rowSettings.relationship == 'parent') {
var previousRow = $(changedRow).prev('tr');
while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) {
previousRow = previousRow.prev('tr');
}
// If we found a row.
if (previousRow.length) {
sourceRow = previousRow[0];
else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) {
if (this.indentEnabled) {
if ($('.indentations', nextRow).size() == $('.indentations', changedRow)) {
sourceRow = nextRow;
}
}
// Otherwise we went all the way to the left of the table without finding
// a parent, meaning this item has been placed at the root level.
else {
// Use the first row in the table as source, because it's garanteed to
// be at the root level. Find the first item, then compare this row
// against it as a sibling.
sourceRow = $('tr.draggable:first').get(0);
if (sourceRow == this.rowObject.element) {
sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
}
var useSibling = true;
sourceRow = nextRow;
}
}
}
// Parents, look up the tree until we find a field not in this group.
// Go up as many parents as indentations in the changed row.
else if (rowSettings.relationship == 'parent') {
var previousRow = $(changedRow).prev('tr');
while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) {
previousRow = previousRow.prev('tr');
}
// If we found a row.
if (previousRow.length) {
sourceRow = previousRow[0];
}
// Otherwise we went all the way to the left of the table without finding
// a parent, meaning this item has been placed at the root level.
else {
// Use the first row in the table as source, because it's garanteed to
// be at the root level. Find the first item, then compare this row
// against it as a sibling.
sourceRow = $('tr.draggable:first').get(0);
if (sourceRow == this.rowObject.element) {
sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
}
var useSibling = true;
}
}
// Because we may have moved the row from one category to another,
// take a look at our sibling and borrow its sources and targets.
this.copyDragClasses(sourceRow, changedRow, group);
rowSettings = this.rowSettings(group, changedRow);
// Because we may have moved the row from one category to another,
// take a look at our sibling and borrow its sources and targets.
this.copyDragClasses(sourceRow, changedRow, group);
rowSettings = this.rowSettings(group, changedRow);
// In the case that we're looking for a parent, but the row is at the top
// of the tree, copy our sibling's values.
if (useSibling) {
rowSettings.relationship = 'sibling';
rowSettings.source = rowSettings.target;
}
// In the case that we're looking for a parent, but the row is at the top
// of the tree, copy our sibling's values.
if (useSibling) {
rowSettings.relationship = 'sibling';
rowSettings.source = rowSettings.target;
}
var targetClass = '.' + rowSettings.target;
var targetElement = $(targetClass, changedRow).get(0);
// Check if a target element exists in this row.
if (targetElement) {
var sourceClass = '.' + rowSettings.source;
var sourceElement = $(sourceClass, sourceRow).get(0);
switch (rowSettings.action) {
case 'depth':
// Get the depth of the target row.
targetElement.value = $('.indentation', $(sourceElement).parents('tr:first')).size();
break;
case 'match':
// Update the value.
targetElement.value = sourceElement.value;
break;
case 'order':
var siblings = this.rowObject.findSiblings(rowSettings);
if ($(targetElement).is('select')) {
// Get a list of acceptable values.
var values = new Array();
$('option', targetElement).each(function() {
values.push(this.value);
});
// Populate the values in the siblings.
$(targetClass, siblings).each(function() {
this.value = values.shift();
});
}
else {
// Assume a numeric input field.
var weight = parseInt($(targetClass, siblings[0]).val()) || 0;
$(targetClass, siblings).each(function() {
this.value = weight;
weight++;
});
}
break;
}
var targetClass = '.' + rowSettings.target;
var targetElement = $(targetClass, changedRow).get(0);
// Check if a target element exists in this row.
if (targetElement) {
var sourceClass = '.' + rowSettings.source;
var sourceElement = $(sourceClass, sourceRow).get(0);
switch (rowSettings.action) {
case 'depth':
// Get the depth of the target row.
targetElement.value = $('.indentation', $(sourceElement).parents('tr:first')).size();
break;
case 'match':
// Update the value.
targetElement.value = sourceElement.value;
break;
case 'order':
var siblings = this.rowObject.findSiblings(rowSettings);
if ($(targetElement).is('select')) {
// Get a list of acceptable values.
var values = new Array();
$('option', targetElement).each(function() {
values.push(this.value);
});
// Populate the values in the siblings.
$(targetClass, siblings).each(function() {
this.value = values.shift();
});
}
else {
// Assume a numeric input field.
var weight = parseInt($(targetClass, siblings[0]).val()) || 0;
$(targetClass, siblings).each(function() {
this.value = weight;
weight++;
});
}
break;
}
}
};
......
......@@ -24,38 +24,6 @@ function comment_update_1() {
return array();
}
function comment_update_6001() {
$ret[] = update_sql("ALTER TABLE {comments} DROP score");
$ret[] = update_sql("ALTER TABLE {comments} DROP users");
return $ret;
}
/**
* Changed comment settings from global to per-node -- copy global
* settings to all node types.
*/
function comment_update_6002() {
$settings = array(
'comment_default_mode' => COMMENT_MODE_THREADED_EXPANDED,
'comment_default_order' => COMMENT_ORDER_NEWEST_FIRST,
'comment_default_per_page' => 50,
'comment_controls' => COMMENT_CONTROLS_HIDDEN,
'comment_anonymous' => COMMENT_ANONYMOUS_MAYNOT_CONTACT,
'comment_subject_field' => 1,
'comment_preview' => COMMENT_PREVIEW_REQUIRED,
'comment_form_location' => COMMENT_FORM_SEPARATE_PAGE,
);
$types = node_get_types();
foreach ($settings as $setting => $default) {
$value = variable_get($setting, $default);
foreach ($types as $type => $object) {
variable_set($setting .'_'. $type, $value);
}
variable_del($setting);
}
return array();
}
/**
* Implementation of hook_schema().
*/
......
......@@ -214,26 +214,33 @@ function forum_admin_settings() {
/**
* Returns an overview list of existing forums and containers
*/
function forum_overview() {
$header = array(t('Name'), t('Operations'));
function forum_overview(&$form_state) {
include_once(drupal_get_path('module', 'taxonomy') .'/taxonomy.admin.inc');
$vid = variable_get('forum_nav_vocabulary', '');
$tree = taxonomy_get_tree($vid);
if ($tree) {
foreach ($tree as $term) {
if (in_array($term->tid, variable_get('forum_containers', array()))) {
$rows[] = array(str_repeat(' -- ', $term->depth) .' '. l($term->name, 'forum/'. $term->tid), l(t('edit container'), 'admin/content/forum/edit/container/'. $term->tid));
$vocabulary = taxonomy_vocabulary_load($vid);
$form = taxonomy_overview_terms($form_state, $vocabulary);
drupal_set_title('Forums');
foreach (element_children($form) as $key) {
if (isset($form[$key]['#term'])) {
$term = $form[$key]['#term'];
$form[$key]['view']['#value'] = l($term['name'], 'forum/'. $term['tid']);
if (in_array($form[$key]['#term']['tid'], variable_get('forum_containers', array()))) {
$form[$key]['edit']['#value'] = l(t('edit container'), 'admin/content/forum/edit/container/'. $term['tid']);
}
else {
$rows[] = array(str_repeat(' -- ', $term->depth) .' '. l($term->name, 'forum/'. $term->tid), l(t('edit forum'), 'admin/content/forum/edit/forum/'. $term->tid));
}
$form[$key]['edit']['#value'] = l(t('edit forum'), 'admin/content/forum/edit/forum/'. $term['tid']);
}
}
}
else {
$rows[] = array(array('data' => '<em>'. t('There are no existing containers or forums. You may add some on the <a href="@container">add container</a> or <a href="@forum">add forum</a> pages.', array('@container' => url('admin/content/forum/add/container'), '@forum' => url('admin/content/forum/add/forum'))) .'</em>', 'colspan' => 2));
}
return theme('table', $header, $rows);
// The form needs to have submit and validate handlers set explicitly.
$form['#theme'] = 'taxonomy_overview_terms';
$form['#submit'] = array('taxonomy_overview_terms_submit'); // Use the existing taxonomy overview submit handler.
$form['#validate'] = array('taxonomy_overview_terms_validate');
$form['#empty_text'] = '<em>'. t('There are no existing containers or forums. You may add some on the <a href="@container">add container</a> or <a href="@forum">add forum</a> pages.', array('@container' => url('admin/content/forum/add/container'), '@forum' => url('admin/content/forum/add/forum'))) .'</em>';
return $form;
}
/**
......
......@@ -92,7 +92,8 @@ function forum_menu() {
$items['admin/content/forum'] = array(
'title' => 'Forums',
'description' => 'Control forums and their hierarchy and change forum settings.',
'page callback' => 'forum_overview',
'page callback' => 'drupal_get_form',
'page arguments' => array('forum_overview'),
'access arguments' => array('administer forums'),
'file' => 'forum.admin.inc',
);
......
......@@ -15,126 +15,6 @@ function locale_install() {
db_query("INSERT INTO {languages} (language, name, native, direction, enabled, weight, javascript) VALUES ('en', 'English', 'English', '0', '1', '0', '')");
}
/**
* @defgroup updates-5.x-to-6.x Locale updates from 5.x to 6.x
* @{
*/
/**
* {locales_meta} table became {languages}.
*/
function locale_update_6001() {
$ret = array();
switch ($GLOBALS['db_type']) {
case 'mysql':
case 'mysqli':
$ret[] = update_sql("CREATE TABLE {languages} (
language varchar(12) NOT NULL default '',
name varchar(64) NOT NULL default '',
native varchar(64) NOT NULL default '',
direction int NOT NULL default '0',
enabled int NOT NULL default '0',
plurals int NOT NULL default '0',
formula varchar(128) NOT NULL default '',
domain varchar(128) NOT NULL default '',
prefix varchar(128) NOT NULL default '',
weight int NOT NULL default '0',
PRIMARY KEY (language)
) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
break;
case 'pgsql':
$ret[] = update_sql("CREATE TABLE {languages} (
language varchar(12) NOT NULL default '',
name varchar(64) NOT NULL default '',
native varchar(64) NOT NULL default '',
direction int NOT NULL default '0',
enabled int NOT NULL default '0',
plurals int NOT NULL default '0',
formula varchar(128) NOT NULL default '',
domain varchar(128) NOT NULL default '',
prefix varchar(128) NOT NULL default '',
weight int NOT NULL default '0',
PRIMARY KEY (language)
)");
break;
}
// Save the languages
$ret[] = update_sql("INSERT INTO {languages} (language, name, native, direction, enabled, plurals, formula, domain, prefix, weight) SELECT locale, name, name, 0, enabled, plurals, formula, '', locale, 0 FROM {locales_meta}");
// Save the language count in the variable table
$count = db_result(db_query('SELECT COUNT(*) FROM {languages} WHERE enabled = 1'));
variable_set('language_count', $count);
// Save the default language in the variable table
$default = db_fetch_object(db_query('SELECT * FROM {locales_meta} WHERE isdefault = 1'));
variable_set('language_default', (object) array('language' => $default->locale, 'name' => $default->name, 'native' => '', 'direction' => 0, 'enabled' => 1, 'plurals' => $default->plurals, 'formula' => $default->formula, 'domain' => '', 'prefix' => $default->locale, 'weight' => 0));
$ret[] = update_sql("DROP TABLE {locales_meta}");
return $ret;
}
/**
* Change locale column to language. The language column is added by
* update_fix_d6_requirements() in update.php to avoid a large number
* of error messages from update.php. All we need to do here is copy
* locale to language and then drop locale.
*/
function locale_update_6002() {
$ret = array();
$ret[] = update_sql('UPDATE {locales_target} SET language = locale');
db_drop_field($ret, 'locales_target', 'locale');
return $ret;
}
/**
* Adds a column to store the filename of the JavaScript translation file.
*/
function locale_update_6003() {
$ret = array();
db_add_field($ret, 'languages', 'javascript', array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''));
return $ret;
}
/**
* Remove empty translations, we don't need these anymore.
*/
function locale_update_6004() {
$ret = array();
$ret[] = update_sql("DELETE FROM {locales_target} WHERE translation = ''");
return $ret;
}
/**
* Prune strings with no translations (will be automatically re-registered if still in use)
*/
function locale_update_6005() {
$ret = array();
$ret[] = update_sql("DELETE FROM {locales_source} WHERE lid NOT IN (SELECT lid FROM {locales_target})");
return $ret;
}
/**
* Fix remaining inconsistent indexes.
*/
function locale_update_6006() {
$ret = array();
db_add_index($ret, 'locales_target', 'language', array('language'));
switch ($GLOBALS['db_type']) {
case 'pgsql':
db_drop_index($ret, 'locales_source', 'source');
db_add_index($ret, 'locales_source', 'source', array(array('source', 30)));
break;
}
return $ret;
}
/**
* @} End of "defgroup updates-5.x-to-6.x"
*/
/**
* Implementation of hook_uninstall().
*/
......
......@@ -2735,6 +2735,387 @@ function system_update_6039() {
}
/**
* Remove unused fields from comment tables
*/
function system_update_6038() {
$ret = array();
if (db_table_exists('comments')) {
$ret[] = update_sql("ALTER TABLE {comments} DROP score");
$ret[] = update_sql("ALTER TABLE {comments} DROP users");
}
return $ret;
}
/**
* Changed comment settings from global to per-node -- copy global
* settings to all node types.
*/
function system_update_6039() {
$ret = array();
$settings = array(
'comment_default_mode' => COMMENT_MODE_THREADED_EXPANDED,
'comment_default_order' => COMMENT_ORDER_NEWEST_FIRST,
'comment_default_per_page' => 50,
'comment_controls' => COMMENT_CONTROLS_HIDDEN,
'comment_anonymous' => COMMENT_ANONYMOUS_MAYNOT_CONTACT,
'comment_subject_field' => 1,
'comment_preview' => COMMENT_PREVIEW_REQUIRED,
'comment_form_location' => COMMENT_FORM_SEPARATE_PAGE,
);
$types = node_get_types();
foreach ($settings as $setting => $default) {
$value = variable_get($setting, $default);
foreach ($types as $type => $object) {
variable_set($setting .'_'. $type, $value);
}
variable_del($setting);
}
return array();
}
/**
* This function moves any existing book hierarchy into the new structure used
* in the 6.x module. Rather than storing the hierarchy in the {book} table,
* the menu API is used to store the hierarchy in the {menu_links} table and the
* {book} table serves to uniquely connect a node to a menu link.
*
* In order to accomplish this, the current hierarchy is processed using a stack.
* The stack insures that each parent is processed before any of its children
* in the book hierarchy, and is compatible with batched update processing.
*
*/
function system_update_6040() {
$ret = array();
if (!db_table_exists('book')) {
return $ret;
}
// Set up for a multi-part update.
if (!isset($_SESSION['system_update_6040'])) {
$schema['book'] = array(
'fields' => array(
'mlid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
'bid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
),
'indexes' => array(
'nid' => array('nid'),
'bid' => array('bid')
),
'primary key' => array('mlid'),
);
// Add the node type.
_book_install_type_create();
// Fix role permissions to account for the changed names
// Setup the array holding strings to match and the corresponding
// strings to replace them with.
$replace = array(
'outline posts in books' => 'administer book outlines',
'create book pages' => 'create book content',
'edit book pages' => 'edit book content',
'edit own book pages' => 'edit own book content',
'see printer-friendly version' => 'access printer-friendly version',
);
// Loop over all the roles, and do the necessary transformations.
$query = db_query("SELECT rid, perm FROM {permission} ORDER BY rid");
while ($role = db_fetch_object($query)) {
// Replace all the old permissions with the corresponding new permissions.
$fixed_perm = strtr($role->perm, $replace);
// If the user could previously create book pages, they should get the new
// 'add content to books' permission.
if (strpos($role->perm, 'create book pages') !== FALSE) {
$fixed_perm .= ', add content to books';
}
// Only save if the permissions have changed.
if ($fixed_perm != $role->perm) {
$ret[] = update_sql("UPDATE {permission} SET perm = '$fixed_perm' WHERE rid = $role->rid");
}
}
// Determine whether there are any existing nodes in the book hierarchy.
if (db_result(db_query("SELECT COUNT(*) FROM {book}"))) {
// Temporary table for the old book hierarchy; we'll discard revision info.
$schema['book_temp'] = array(
'fields' => array(
'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
'parent' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny')
),
'indexes' => array(