- Patch #146425 by pwolanin et al:
* Removes the hard-coded 'book' type and perform all node actions equally on any node type via hook_nodeapi. * Achieves 100% integration with the menu system. Improves performance of book rendering. * All the algorithms have been changed to use the tree data structure returned by the menu system. * Added support for 'multiple books'. * Some UI improvements. This is a momumental patch that took 69 iterations. Although there is room for improvement, this is a big step forward. Thanks for the persistence, pwolanin.6.x
parent
7b682ae9f8
commit
e7f195bbee
|
@ -1,6 +1,6 @@
|
|||
; $Id$
|
||||
name = Book
|
||||
description = Allows users to collaboratively author a book.
|
||||
description = Allows users to structure site pages in a hierarchy or outline.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
|
|
@ -7,12 +7,239 @@
|
|||
function book_install() {
|
||||
// Create tables.
|
||||
drupal_install_schema('book');
|
||||
// Add the node type.
|
||||
_book_install_type_create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function book_uninstall() {
|
||||
// Delete menu links.
|
||||
db_query("DELETE FROM {menu_links} WHERE module = 'book'");
|
||||
menu_cache_clear_all();
|
||||
// Remove tables.
|
||||
drupal_uninstall_schema('book');
|
||||
}
|
||||
|
||||
function _book_install_type_create() {
|
||||
// Create an additional node type
|
||||
$book_node_type = array(
|
||||
'type' => 'book',
|
||||
'name' => t('Book page'),
|
||||
'module' => 'node',
|
||||
'description' => t("A static page. These posts (as well as other types) may be added to a book outline to create a hierarchical structure for your site."),
|
||||
'custom' => TRUE,
|
||||
'modified' => TRUE,
|
||||
'locked' => FALSE,
|
||||
);
|
||||
|
||||
$book_node_type = (object)_node_type_set_defaults($book_node_type);
|
||||
node_type_save($book_node_type);
|
||||
// Default to not promoted.
|
||||
variable_set('node_options_book', array('status'));
|
||||
// Use this default type for adding content to books.
|
||||
variable_set('book_allowed_types', array('book'));
|
||||
variable_set('book_child_type', 'book');
|
||||
}
|
||||
|
||||
/**
|
||||
* Drupal 5.x to 6.x update.
|
||||
*
|
||||
* 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 book_update_6000() {
|
||||
$ret = array();
|
||||
|
||||
// Set up for a multi-part update.
|
||||
if (!isset($_SESSION['book_update_6000'])) {
|
||||
|
||||
$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(
|
||||
'parent' => array('parent')
|
||||
),
|
||||
'primary key' => array('nid'),
|
||||
);
|
||||
|
||||
db_create_table($ret, 'book_temp', $schema['book_temp']);
|
||||
|
||||
// Insert each node in the old table into the temporary table.
|
||||
$ret[] = update_sql("INSERT INTO {book_temp} (nid, parent, weight) SELECT b.nid, b.parent, b.weight FROM {book} b INNER JOIN {node} n on b.vid = n.vid");
|
||||
$ret[] = update_sql("DROP TABLE {book}");
|
||||
|
||||
db_create_table($ret, 'book', $schema['book']);
|
||||
|
||||
$_SESSION['book_update_6000_orphans']['from'] = 0;
|
||||
$_SESSION['book_update_6000'] = array();
|
||||
$result = db_query("SELECT * from {book_temp} WHERE parent = 0");
|
||||
|
||||
// Collect all books - top-level nodes.
|
||||
while ($a = db_fetch_array($result)) {
|
||||
$_SESSION['book_update_6000'][] = $a;
|
||||
}
|
||||
$ret['#finished'] = FALSE;
|
||||
return $ret;
|
||||
}
|
||||
else {
|
||||
// No exising nodes in the hierarchy, so drop the table and re-create it.
|
||||
$ret[] = update_sql("DROP TABLE {book}");
|
||||
db_create_table($ret, 'book', $schema['book']);
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
elseif ($_SESSION['book_update_6000_orphans']) {
|
||||
// Do the first batched part of the update - collect orphans.
|
||||
$update_count = 400; // Update this many at a time
|
||||
|
||||
$result = db_query_range("SELECT * FROM {book_temp}", $_SESSION['book_update_6000_orphans']['from'], $update_count);
|
||||
|
||||
if (db_num_rows($result)) {
|
||||
$_SESSION['book_update_6000_orphans']['from'] += $update_count;
|
||||
}
|
||||
else {
|
||||
// Done with this part
|
||||
if (!empty($_SESSION['book_update_6000_orphans']['book'])) {
|
||||
// The orphans' parent is added last, so it will be processed first.
|
||||
$_SESSION['book_update_6000'][] = $_SESSION['book_update_6000_orphans']['book'];
|
||||
}
|
||||
$_SESSION['book_update_6000_orphans'] = FALSE;
|
||||
}
|
||||
// Go through the next $update_count book pages and locate the orphans.
|
||||
while ($book = db_fetch_array($result)) {
|
||||
// Orphans are defined as nodes whose parent does not exist in the table.
|
||||
if ($book['parent'] && !db_result(db_query("SELECT COUNT(*) FROM {book_temp} WHERE nid = %d", $book['parent']))) {
|
||||
if (empty($_SESSION['book_update_6000_orphans']['book'])) {
|
||||
// The first orphan becomes the parent for all other orphans.
|
||||
$book['parent'] = 0;
|
||||
$_SESSION['book_update_6000_orphans']['book'] = $book;
|
||||
$ret[] = array('success' => TRUE, 'query' => t('Relocated orphan book pages.'));
|
||||
}
|
||||
else {
|
||||
// Re-assign the parent value of the book, and add it to the stack.
|
||||
$book['parent'] = $_SESSION['book_update_6000_orphans']['book']['nid'];
|
||||
$_SESSION['book_update_6000'][] = $book;
|
||||
}
|
||||
}
|
||||
}
|
||||
$ret['#finished'] = FALSE;
|
||||
return $ret;
|
||||
}
|
||||
else {
|
||||
// Do the next batched part of the update
|
||||
$update_count = 100; // Update this many at a time
|
||||
|
||||
while ($update_count && $_SESSION['book_update_6000']) {
|
||||
// Get the last node off the stack.
|
||||
$book = array_pop($_SESSION['book_update_6000']);
|
||||
|
||||
// Add all of this node's children to the stack
|
||||
$result = db_query("SELECT * FROM {book_temp} WHERE parent = %d", $book['nid']);
|
||||
while ($a = db_fetch_array($result)) {
|
||||
$_SESSION['book_update_6000'][] = $a;
|
||||
}
|
||||
|
||||
if ($book['parent']) {
|
||||
// If its not a top level page, get its parent's mlid.
|
||||
$parent = db_fetch_array(db_query("SELECT b.mlid AS plid, b.bid FROM {book} b WHERE b.nid = %d", $book['parent']));
|
||||
$book = array_merge($book, $parent);
|
||||
}
|
||||
else {
|
||||
// There is not a parent - this is a new book.
|
||||
$book['plid'] = 0;
|
||||
$book['bid'] = $book['nid'];
|
||||
}
|
||||
|
||||
$book += array(
|
||||
'module' => 'book',
|
||||
'link_path' => 'node/'. $book['nid'],
|
||||
'router_path' => 'node/%',
|
||||
'menu_name' => book_menu_name($book['bid']),
|
||||
);
|
||||
$book = array_merge($book, db_fetch_array(db_query("SELECT title AS link_title FROM {node} WHERE nid = %d", $book['nid'])));
|
||||
|
||||
// Items with depth > MENU_MAX_DEPTH cannot be saved.
|
||||
if (menu_link_save($book)) {
|
||||
db_query("INSERT INTO {book} (mlid, nid, bid) VALUES (%d, %d, %d)", $book['mlid'], $book['nid'], $book['bid']);
|
||||
}
|
||||
else {
|
||||
// The depth was greater then MENU_MAX_DEPTH, so attach it to the
|
||||
// closest valid parent.
|
||||
$book['plid'] = db_result(db_query("SELECT plid FROM {menu_links} WHERE mlid = %d", $book['plid']));
|
||||
if (menu_link_save($book)) {
|
||||
db_query("INSERT INTO {book} (mlid, nid, bid) VALUES (%d, %d, %d)", $book['mlid'], $book['nid'], $book['bid']);
|
||||
}
|
||||
}
|
||||
$update_count--;
|
||||
}
|
||||
$ret['#finished'] = FALSE;
|
||||
}
|
||||
|
||||
if (empty($_SESSION['book_update_6000'])) {
|
||||
$ret['#finished'] = TRUE;
|
||||
$ret[] = array('success' => TRUE, 'query' => t('Relocated existing book pages.'));
|
||||
$ret[] = update_sql("DROP TABLE {book_temp}");
|
||||
unset($_SESSION['book_update_6000']);
|
||||
unset($_SESSION['book_update_6000_orphans']);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// $Id$
|
||||
|
||||
Drupal.behaviors.bookSelect = function(context) {
|
||||
// This behavior attaches by ID, so is only valid once on a page.
|
||||
if ($('#edit-book-bid.book-select-processed').size()) {
|
||||
return;
|
||||
}
|
||||
// Hide the button in the node form, since it's not needed when JS is enabled.
|
||||
$('#edit-book-pick-book').css('display', 'none');
|
||||
|
||||
// Binds a function to the keyup and change actions of the book select to
|
||||
// retrieve parent options. Mark as processed so this binding is only done once.
|
||||
$('#edit-book-bid')
|
||||
.keyup(Drupal.bookFillSelect)
|
||||
.change(Drupal.bookFillSelect)
|
||||
.addClass('book-select-processed');
|
||||
}
|
||||
|
||||
// This function passes the form information and the book ID to a Drupal callback
|
||||
// and retrieves a parent select with changed options to replace the one in the form.
|
||||
Drupal.bookFillSelect = function() {
|
||||
// Create a progress bar and substitute it for the parent select.
|
||||
pb = new Drupal.progressBar('book_progress');
|
||||
pb.setProgress(-1, Drupal.t('Updating parents...'));
|
||||
$('#edit-book-plid-wrapper').html(pb.element);
|
||||
|
||||
$.get(Drupal.settings.book.formCallback +'/'+ $('#'+ Drupal.settings.book.formId +' input[@name=form_build_id]').val() +'/'+ $('#edit-book-bid').val(), {}, function(data) {
|
||||
parsedData = Drupal.parseJson(data);
|
||||
// Insert the new select, and remove the progress bar.
|
||||
$('#edit-book-plid-wrapper').after(parsedData['book']).remove();
|
||||
});
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -4,16 +4,15 @@
|
|||
function book_schema() {
|
||||
$schema['book'] = array(
|
||||
'fields' => array(
|
||||
'vid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
|
||||
'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')
|
||||
'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'),
|
||||
'parent' => array('parent')
|
||||
'nid' => array('nid'),
|
||||
'bid' => array('bid')
|
||||
),
|
||||
'primary key' => array('vid'),
|
||||
'primary key' => array('mlid'),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
|
|
|
@ -14,6 +14,7 @@ define('NODE_BUILD_PREVIEW', 1);
|
|||
define('NODE_BUILD_SEARCH_INDEX', 2);
|
||||
define('NODE_BUILD_SEARCH_RESULT', 3);
|
||||
define('NODE_BUILD_RSS', 4);
|
||||
define('NODE_BUILD_PRINT', 5);
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
|
@ -2231,8 +2232,8 @@ function node_form(&$form_state, $node) {
|
|||
}
|
||||
|
||||
function node_form_build_preview($form, &$form_state) {
|
||||
// We do not want to execute button level handlers, we want the form level
|
||||
// handlers to go in and change the submitted values.
|
||||
// Unset any button-level handlers, execute all the form-level submit functions
|
||||
// to process the form values into an updated node, and rebuild the form.
|
||||
unset($form_state['submit_handlers']);
|
||||
form_execute_handlers('submit', $form, $form_state);
|
||||
|
||||
|
@ -2390,8 +2391,8 @@ function theme_node_log_message($log) {
|
|||
function node_form_submit($form, &$form_state) {
|
||||
global $user;
|
||||
|
||||
// We do not want to execute button level handlers, we want the form level
|
||||
// handlers to go in and change the submitted values.
|
||||
// Unset any button-level handlers, and execute all the form-level submit
|
||||
// functions to process the form values into an updated node.
|
||||
unset($form_state['submit_handlers']);
|
||||
form_execute_handlers('submit', $form, $form_state);
|
||||
|
||||
|
|
Loading…
Reference in New Issue