Index: install.php =================================================================== RCS file: /cvs/drupal/drupal/install.php,v retrieving revision 1.6 diff -u -p -r1.6 install.php --- install.php 8 Aug 2006 21:18:02 -0000 1.6 +++ install.php 15 Aug 2006 06:12:20 -0000 @@ -114,6 +114,9 @@ function install_verify_settings() { */ function install_change_settings() { global $profile, $db_url, $db_type, $db_prefix; + // see http://drupal.org/node/74992 + // temporary fix. + $profile = 'default'; $url = parse_url($db_url); $db_user = urldecode($url['user']); Index: modules/book/book.install =================================================================== RCS file: /cvs/drupal/drupal/modules/book/book.install,v retrieving revision 1.4 diff -u -p -r1.4 book.install --- modules/book/book.install 15 Aug 2006 05:25:19 -0000 1.4 +++ modules/book/book.install 15 Aug 2006 06:12:20 -0000 @@ -5,26 +5,85 @@ function book_install() { switch ($GLOBALS['db_type']) { case 'mysql': case 'mysqli': - db_query("CREATE TABLE {book} ( + db_query("CREATE TABLE {book_pages} ( vid int(10) unsigned NOT NULL default '0', nid int(10) unsigned NOT NULL default '0', + bid int(10) unsigned NOT NULL default '0', parent int(10) NOT NULL default '0', weight tinyint(3) NOT NULL default '0', PRIMARY KEY (vid), KEY nid (nid), + KEY bid (bid), KEY parent (parent) ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); + db_query("CREATE TABLE {book} ( + bid INT( 10 ) UNSIGNED NOT NULL default '0' PRIMARY KEY , + nid INT( 10 ) UNSIGNED NOT NULL default '0' , + book_author VARCHAR(255) NOT NULL default '' , + book_license LONGTEXT NOT NULL default '' , + weight TINYINT( 3 ) NOT NULL default '0', + KEY nid (nid) + ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); break; case 'pgsql': - db_query("CREATE TABLE {book} ( + db_query("CREATE TABLE {book_pages} ( vid int_unsigned NOT NULL default '0', nid int_unsigned NOT NULL default '0', - parent int NOT NULL default '0', + bid int_unsigned NOT NULL default '0', + parent int_unsigned NOT NULL default '0', weight smallint NOT NULL default '0', PRIMARY KEY (vid) + "); + db_query("CREATE TABLE {book} ( + bid int_unsigned NOT NULL default '0', + nid int_unsigned NOT NULL default '0', + book_author varchar(255) NOT NULL default '', + book_license text NOT NULL default '', + weight smallint NOT NULL default '', )"); + db_query("CREATE INDEX {book_pages}_nid_idx ON {book_pages} (nid)"); + db_query("CREATE INDEX {book_pages}_xid_idx ON {book_pages} (bid)"); + db_query("CREATE INDEX {book_pages}_parent_idx ON {book_pages} (parent)"); db_query("CREATE INDEX {book}_nid_idx ON {book} (nid)"); - db_query("CREATE INDEX {book}_parent_idx ON {book} (parent)"); + db_query("CREATE INDEX {book}_bid_idx ON {book} (bid)"); + break; + } +} + +/** + * Update book table definitions. + */ +function book_update_1000() { + $ret = array(); + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + $ret[] = update_sql("ALTER TABLE `{book}` RENAME `{book_pages}`;"); + $ret[] = update_sql("CREATE TABLE `{book}` ( + `bid` INT( 10 ) UNSIGNED NOT NULL default '0' PRIMARY KEY , + `nid` INT( 10 ) UNSIGNED NOT NULL default '0' , + `book_author` VARCHAR(255) NOT NULL default '' , + `book_license` LONGTEXT NOT NULL default '' , + `weight` TINYINT( 3 ) NOT NULL default '0', + KEY nid (nid) + ) TYPE = MYISAM"); + $ret[] = update_sql("ALTER TABLE `{book_pages}` ADD `bid` INT( 10 ) UNSIGNED NOT NULL AFTER `nid` ;"); + $ret[] = update_sql("ALTER TABLE `{book_pages}` ADD INDEX ( `bid` );"); + break; + case 'pgsql': + $ret[] = update_sql("ALTER TABLE {book} RENAME {book_pages};"); + $ret[] = update_sql("CREATE TABLE {book} ( + bid int_unsigned NOT NULL default '0' , + nid int_unsigned NOT NULL default '0' , + book_author varchar(255) NOT NULL default '' , + book_license text NOT NULL default '' , + weight smallint NOT NULL default '0' + "); + $ret[] = update_sql("CREATE INDEX {book}_bid_idx ON {book}(bid);"); + $ret[] = update_sql("CREATE INDEX {book}_uid_idx ON {book}(uid);"); + db_add_column($ret, 'book_pages', 'bid', 'int', array('not null' => TRUE, 'default' => 0)); + $ret[] = update_sql("CREATE INDEX {book_pages}_bid_idx ON {book_pages}(bid);"); break; } + return $ret; } Index: modules/book/book.module =================================================================== RCS file: /cvs/drupal/drupal/modules/book/book.module,v retrieving revision 1.382 diff -u -p -r1.382 book.module --- modules/book/book.module 14 Aug 2006 07:14:49 -0000 1.382 +++ modules/book/book.module 15 Aug 2006 06:12:22 -0000 @@ -14,8 +14,14 @@ function book_node_info() { 'book' => array( 'name' => t('book page'), 'module' => 'book', - 'description' => t("A book is a collaborative writing effort: users can collaborate writing the pages of the book, positioning the pages in the right order, and reviewing or modifying pages previously written. So when you have some information to share or when you read a page of the book and you didn't like it, or if you think a certain page could have been written better, you can do something about it."), - ) + 'description' => t("A book is a collaborative writing effort: users can collaborate writing the pages of the book, positioning the pages in the right order, and reviewing or modifying pages previously written. So when you have some information to share or when you read a page of the book and you didn't like it, or if you think a certain page could have been written better, you can do something about it.
+ Use this link to select a book to add a page to, otherwise your book page will be created independently of any existing book.", array('%url' => url('node/add/book/select_book'))), + ), + 'book_cover' => array( + 'name' => t('book cover'), + 'module' => 'book_cover', + 'description' => t('Add a new book.'), + ), ); } @@ -26,8 +32,38 @@ function book_perm() { return array('outline posts in books', 'create book pages', 'create new books', 'edit book pages', 'edit own book pages', 'see printer-friendly version'); } + +/** + * Implementation of hook_access() for a book cover. + */ +function book_cover_access($op, $node) { + global $user; + + if ($op == 'create') { + // Only registered users can create books. Given the nature + // of the book module this is considered to be a good/safe idea. + return user_access('create new books'); + } + + if ($op == 'update') { + // Only registered users can update book pages. Given the nature + // of the book module this is considered to be a good/safe idea. + // One can only update a book page if there are no suggested updates + // of that page waiting for approval. That is, only updates that + // don't overwrite the current or pending information are allowed. + + if ($node->uid == $user->uid && user_access('edit own book pages')) { + return TRUE; + } + else { + // do nothing. node-access() will determine further access + } + } +} + + /** - * Implementation of hook_access(). + * Implementation of hook_access() for a book page. */ function book_access($op, $node) { global $user; @@ -61,6 +97,16 @@ function book_link($type, $node = NULL, $links = array(); + if ($type == 'node' && $node->type == 'book_cover') { + if (user_access('create new books', $node)) { + $links['book_add_child'] = array( + 'title' => t('add child page'), + 'href' => "node/add/book/bid/$node->bid" + ); + } + return $links; + } + if ($type == 'node' && isset($node->parent)) { if (!$teaser) { if (book_access('create', $node)) { @@ -107,10 +153,10 @@ function book_menu($may_cache) { 'weight' => 8); $items[] = array( 'path' => 'book', - 'title' => t('books'), - 'callback' => 'book_render', - 'access' => user_access('access content'), - 'type' => MENU_SUGGESTED_ITEM); + 'title' => t('book shelf'), + 'callback' => 'book_shelf', + 'type' => MENU_NORMAL_ITEM, + 'access' => user_access('access content')); $items[] = array( 'path' => 'book/export', 'callback' => 'book_export', @@ -122,12 +168,30 @@ function book_menu($may_cache) { // We put this in !$may_cache so it's only added once per request drupal_add_css(drupal_get_path('module', 'book') .'/book.css'); + $items[] = array( + 'path' => 'node/add/book/select_book', + 'title' => t('New page'), + 'callback' => '_add_page_select_book', + 'access' => user_access('create book pages'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 2); // To avoid SQL overhead, check whether we are on a node page and whether the // user is allowed to outline posts in books. if (arg(0) == 'node' && is_numeric(arg(1)) && user_access('outline posts in books')) { // Only add the outline-tab for non-book pages: - $result = db_query(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.nid = %d AND n.type != 'book'"), arg(1)); - if (db_num_rows($result) > 0) { + $result = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.type FROM {node} n WHERE n.nid = %d'), arg(1))); + if ($result->type == 'book') { + // If it's a book page, add the 'book' tab. + $items[] = array( + 'path' => 'node/'.arg(1).'/move_to_book', + 'title' => t('book'), + 'callback' => 'move_page_to_book', + 'access' => user_access('outline posts in books'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 8); + } + elseif ($result->type != 'book_cover') { + // add outline tab $items[] = array( 'path' => 'node/'. arg(1) .'/outline', 'title' => t('outline'), @@ -144,6 +208,90 @@ function book_menu($may_cache) { } /** + * Menu call back: Select a book to move a page to. + * @param: + * outline: TRUE if it is a non-book page. + */ +function move_page_to_book($outline = FALSE) { + $result = db_query('SELECT b.bid, n.title FROM {book} b JOIN {node} n ON n.nid = b.nid ORDER BY b.weight, n.title'); + $books = array(); + $books[0] = t('Independent, top-level book page'); + while ($book = db_fetch_object($result)) { + $books[$book->bid] = $book->title; + } + $nid = arg(1); + $bid = db_result(db_query('SELECT bid FROM {book_pages} WHERE nid = %d', $nid)); + $form = array(); + $form['bid'] = array( + '#type' => 'select', + '#title' => t('Select a book'), + '#default_value' => $bid, + '#options' => $books, + '#description' => t('Select the book you wish to move your page to.'), + ); + + $form['outline'] = array( + '#type' => 'value', + '#value' => FALSE, + ); + $form['nid'] = array( + '#type' => 'value', + '#value' => $nid, + ); + $form['children_note'] = array( + '#value' => t('

Note: All of this pages\'s children will be moved together to the new book. If you do not wish that, please update the outline of the children pages first.

'), + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Submit'), + ); + + //override some of the above defaults if it is not a book_page node + if ($outline) { + $form['outline']['#value'] = TRUE; + $form['children_note'] = NULL; + } + + $output = drupal_get_form('move_page_to_book', $form); + return $output; +} + +/** + * Menu call back: Select a book to move a page to. + */ +function move_page_to_book_submit($form_id, $form) { + + if($form['outline']) { + $node = node_load($form['nid']); + db_query('INSERT INTO {book_pages} (nid, vid, bid, parent, weight) VALUES (%d, %d, %d, %d, %d)', $node->nid, $node->vid, $form['bid'], $form['parent'], $form['weight']); + db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $form['log'], $node->vid); + drupal_set_message(t('The post has been added to the book. Now update where in the book you want to add the post.')); + drupal_goto('node/'. $form['nid'].'/outline'); + } + + $result = db_query('UPDATE {book_pages} SET bid = %d, parent = 0 WHERE nid = %d', $form['bid'], $form['nid']); + book_update_children_bid($form['nid'], $form['bid']); + drupal_set_message(t('The page and its children have been relocated.
You can now edit the page\'s location within the new book.')); + drupal_goto('node/'. $form['nid'].'/edit'); +} + +/** + * Helper function to recursively update $bid for each children + * when moving the parent to a new book. + */ +function book_update_children_bid($parent, $bid, $i=1) { + $sql = "SELECT DISTINCT(nid) FROM {book_pages} WHERE parent = %d"; + $result[$i] = db_query($sql,$parent); + if (db_num_rows($result[$i]) > 0) { + while ($page = db_fetch_object($result[$i])) { + $sql = "UPDATE {book_pages} SET bid = %d WHERE nid = %d"; + $result[$i+1] = db_query($sql, $bid, $page->nid); + book_update_children_bid($page->nid, $bid, $i+2); + } + } +} + +/** * Implementation of hook_block(). * * Displays the book table of contents in a block when the current page is a @@ -152,13 +300,31 @@ function book_menu($may_cache) { function book_block($op = 'list', $delta = 0) { $block = array(); if ($op == 'list') { - $block[0]['info'] = t('Book navigation'); + $block['navigation']['info'] = t('Book navigation'); + $block['license']['info'] = t('Book license'); return $block; } else if ($op == 'view') { + // Book license block + // Only display this block when the user is browsing a book page: + // (the block will not appear on the book cover. Maybe it should but it's not implemented yet. + // being able to call global $node and check on $node->bid would be much more convenient.) + if ($delta == 'license') { + if (arg(0) == 'node' && is_numeric(arg(1))) { + $result = db_query(db_rewrite_sql('SELECT b.book_license, n.title, nr.format FROM {book} b JOIN {book_pages} bp ON bp.bid = b.bid JOIN {node} n ON n.nid = b.nid JOIN {node_revisions} nr ON nr.vid = n.vid WHERE bp.nid = %d'), arg(1)); + if (db_num_rows($result) > 0) { + $book = db_fetch_object($result); + $block['subject'] = t('License for the book %book_title', array('%book_title' => theme('placeholder', check_plain($book->title)))); + $block['content'] = check_markup($book->book_license, $book->format, FALSE); + } + } + return $block; + } + + // Book navigation block // Only display this block when the user is browsing a book: if (arg(0) == 'node' && is_numeric(arg(1))) { - $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), arg(1)); + $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE n.nid = %d'), arg(1)); if (db_num_rows($result) > 0) { $node = db_fetch_object($result); @@ -180,17 +346,40 @@ function book_block($op = 'list', $delta } /** + * Implementation of hook_load() for a book cover. + */ +function book_cover_load($node) { + return db_fetch_object(db_query('SELECT * FROM {book} WHERE nid = %d', $node->nid)); +} + + +/** * Implementation of hook_load(). */ function book_load($node) { - return db_fetch_object(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid)); + return db_fetch_object(db_query('SELECT * FROM {book_pages} WHERE vid = %d', $node->vid)); +} + +/** + * Implementation of hook_insert() for book pages. + */ +function book_cover_insert($node) { + $bid = db_next_id('book_bid'); + db_query('INSERT INTO {book} (bid, nid, book_author, book_license, weight) VALUES (%d, %d, "%s", "%s", %d)', $bid, $node->nid, $node->book_author, $node->book_license, $node->weight); } /** - * Implementation of hook_insert(). + * Implementation of hook_insert() for book pages. */ function book_insert($node) { - db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight); + db_query("INSERT INTO {book_pages} (nid, bid, vid, parent, weight) VALUES (%d, %d, %d, %d, %d)", $node->nid, $node->bid, $node->vid, $node->parent, $node->weight); +} + +/** + * Implementation of hook_update() for book covers. + */ +function book_cover_update($node) { + db_query('UPDATE {book} SET book_author = "%s", weight = %d, book_license = "%s" WHERE bid = %d', $node->book_author, $node->weight, $node->book_license, $node->bid); } /** @@ -198,22 +387,37 @@ function book_insert($node) { */ function book_update($node) { if ($node->revision) { - db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight); + db_query("INSERT INTO {book_pages} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight); } else { - db_query("UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d", $node->parent, $node->weight, $node->vid); + db_query("UPDATE {book_pages} SET parent = %d, weight = %d WHERE vid = %d", $node->parent, $node->weight, $node->vid); } } /** + * Implementation of hook_delete() for book covers. + */ +function book_cover_delete(&$node) { + db_query('UPDATE {book_pages} SET bid = 0 WHERE bid = %d', $node->bid); + db_query('DELETE FROM {book} WHERE nid = %d', $node->nid); +} + +/** * Implementation of hook_delete(). */ function book_delete(&$node) { - db_query('DELETE FROM {book} WHERE nid = %d', $node->nid); + db_query('DELETE FROM {book_pages} WHERE nid = %d', $node->nid); } /** - * Implementation of hook_submit(). + * Implementation of hook_submit() for book covers. + */ +function book_cover_submit(&$node) { + global $form; + +} +/** + * Implementation of hook_submit() for book pages. */ function book_submit(&$node) { global $user; @@ -227,23 +431,98 @@ function book_submit(&$node) { } /** - * Implementation of hook_form(). + * Implementation of hook_form() for a book. + */ +function book_cover_form(&$node) { + + $form['title'] = array('#type' => 'textfield', + '#title' => t('Title'), + '#required' => TRUE, + '#default_value' => $node->title, + '#weight' => -5, + '#description' => t('Title of the whole book.'), + ); + $form['body_filter']['body'] = array('#type' => 'textarea', + '#title' => t('Body'), + '#default_value' => $node->body, + '#rows' => 20, + '#required' => TRUE, + '#description' => t('The content of book cover.'), + ); + $form['body_filter']['format'] = filter_form($node->format); + $form['book_author'] = array('#type' => 'textfield', + '#title' => t('Author'), + '#required' => FALSE, + '#default_value' => $node->book_author, + '#description' => t('The author of all the book pages that will be added to this book. The author can be a single person, or a collective. Leave blank if you do not wish to display the authoring information on the book cover.'), + ); + $form['book_license'] = array('#type' => 'textarea', + '#title' => t('License'), + '#default_value' => $node->book_license, + '#rows' => 8, + '#required' => FALSE, + '#description' => t('The license of all the book pages within this book. This information can be displayed within a block on each relevant book page. Leave blank if you do not wish to display any license information. The input format is the same as the one selected for the body.'), + ); + + return $form; +} + + +/** + * Implementation of hook_form() for a book page. */ function book_form(&$node) { + + // looking for url like node/add/book/bid/$bid + if (arg(3) == 'bid') { + if (is_numeric(arg(4))) { + $node->bid = arg(4); + $node->parent = 0; + } + } + // url like node/add/book/parent/$nid + elseif (arg(3) == 'parent') { + $node->parent = arg(4); + $sql = 'SELECT bid FROM {book_pages} where nid = %d'; + $result = db_query($sql, $node->parent); + $node->bid = db_result($result); + } + elseif (arg(2) == 'book') { + drupal_goto('node/add/book/select_book'); + } + + // check that the book exists. + if (!$result = db_result(db_query('SELECT n.title, n.nid FROM {book} AS b JOIN {node} AS n ON n.nid = b.nid WHERE b.bid = %d', $node->bid))) { + $node->bid = 0; + $form_parent_title = t('Parent'); + } + else { + $form_parent_title = t('Position in the book %book', array('%book' => theme('placeholder', $result))); + } + + $form['bid'] = array ('#type' => 'hidden', + '#value' => $node->bid, + ); + $type = node_get_types('type', $node); if ($node->nid && !$node->parent && !user_access('create new books')) { $form['parent'] = array('#type' => 'value', '#value' => $node->parent); } else { $form['parent'] = array('#type' => 'select', - '#title' => t('Parent'), + '#title' => $form_parent_title, '#default_value' => ($node->parent ? $node->parent : arg(4)), - '#options' => book_toc($node->nid), + '#options' => book_toc($node->nid, $node->bid), '#weight' => -4, - '#description' => user_access('create new books') ? t('The parent section in which to place this page. Note that each page whose parent is <top-level> is an independent, top-level book.') : t('The parent that this page belongs in.'), + '#description' => t('The parent that this page belongs in. "Top level" means the highest level within this book.'), ); } + if (!isset($node->parent)) { + $node->parent = 0; + } + + $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#required' => TRUE, @@ -292,10 +571,15 @@ function book_outline($nid) { $node = node_load($nid); $page = book_load($node); + if (!$page) { + return move_page_to_book(TRUE); + } + + $form['parent'] = array('#type' => 'select', '#title' => t('Parent'), '#default_value' => $page->parent, - '#options' => book_toc($node->nid), + '#options' => book_toc($node->nid, $page->bid), '#description' => t('The parent page in the book.'), ); $form['weight'] = array('#type' => 'weight', @@ -312,6 +596,7 @@ function book_outline($nid) { $form['nid'] = array('#type' => 'value', '#value' => $nid); if ($page->nid) { + $form['change-book'] = array('#value' => t('

If you wish to move this node to another book, you must first remove the node from the book outline, then add it back to the book you wish.

')); $form['update'] = array('#type' => 'submit', '#value' => t('Update book outline'), ); @@ -336,17 +621,17 @@ function book_outline_submit($form_id, $ switch ($op) { case t('Add to book outline'): - db_query('INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)', $node->nid, $node->vid, $form_values['parent'], $form_values['weight']); + db_query('INSERT INTO {book_pages} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)', $node->nid, $node->vid, $form_values['parent'], $form_values['weight']); db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $form_values['log'], $node->vid); drupal_set_message(t('The post has been added to the book.')); break; case t('Update book outline'): - db_query('UPDATE {book} SET parent = %d, weight = %d WHERE vid = %d', $form_values['parent'], $form_values['weight'], $node->vid); + db_query('UPDATE {book_pages} SET parent = %d, weight = %d WHERE vid = %d', $form_values['parent'], $form_values['weight'], $node->vid); db_query("UPDATE {node_revisions} SET log = '%s' WHERE vid = %d", $form_values['log'], $node->vid); drupal_set_message(t('The book outline has been updated.')); break; case t('Remove from book outline'): - db_query('DELETE FROM {book} WHERE nid = %d', $node->nid); + db_query('DELETE FROM {book_pages} WHERE nid = %d', $node->nid); drupal_set_message(t('The post has been removed from the book.')); break; } @@ -366,7 +651,7 @@ function book_outline_submit($form_id, $ * */ function book_location($node, $nodes = array()) { - $parent = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), $node->parent)); + $parent = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE n.nid = %d'), $node->parent)); if (isset($parent->title)) { $nodes = book_location($parent, $nodes); $nodes[] = $parent; @@ -378,7 +663,7 @@ function book_location($node, $nodes = a * Accumulates the nodes up to the root of the book from the given node in the $nodes array. */ function book_location_down($node, $nodes = array()) { - $last_direct_child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND b.parent = %d ORDER BY b.weight DESC, n.title DESC'), $node->nid)); + $last_direct_child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE n.status = 1 AND b.parent = %d ORDER BY b.weight DESC, n.title DESC'), $node->nid)); if ($last_direct_child) { $nodes[] = $last_direct_child; $nodes = book_location_down($last_direct_child, $nodes); @@ -396,7 +681,7 @@ function book_prev($node) { } // Previous on the same level: - $direct_above = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND (b.weight < %d OR (b.weight = %d AND n.title < '%s')) ORDER BY b.weight DESC, n.title DESC"), $node->parent, $node->weight, $node->weight, $node->title)); + $direct_above = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 AND (b.weight < %d OR (b.weight = %d AND n.title < '%s')) ORDER BY b.weight DESC, n.title DESC"), $node->parent, $node->weight, $node->weight, $node->title)); if ($direct_above) { // Get last leaf of $above. $path = book_location_down($direct_above); @@ -405,7 +690,7 @@ function book_prev($node) { } else { // Direct parent: - $prev = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d AND n.status = 1'), $node->parent)); + $prev = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE n.nid = %d AND n.status = 1 AND n.moderate = 0'), $node->parent)); return $prev; } } @@ -415,7 +700,7 @@ function book_prev($node) { */ function book_next($node) { // get first direct child - $child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 ORDER BY b.weight ASC, n.title ASC'), $node->nid)); + $child = db_fetch_object(db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 ORDER BY b.weight ASC, n.title ASC'), $node->nid)); if ($child) { return $child; } @@ -425,7 +710,7 @@ function book_next($node) { $path[] = $node; while (($leaf = array_pop($path)) && count($path)) { - $next = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND (b.weight > %d OR (b.weight = %d AND n.title > '%s')) ORDER BY b.weight ASC, n.title ASC"), $leaf->parent, $leaf->weight, $leaf->weight, $leaf->title)); + $next = db_fetch_object(db_query(db_rewrite_sql("SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE b.parent = %d AND n.status = 1 AND n.moderate = 0 AND (b.weight > %d OR (b.weight = %d AND n.title > '%s')) ORDER BY b.weight ASC, n.title ASC"), $leaf->parent, $leaf->weight, $leaf->weight, $leaf->title)); if ($next) { return $next; } @@ -444,6 +729,90 @@ function book_content($node, $teaser = F } /** +* Theme the name of the Author(s) on the book cover +*/ +function theme_book_author($author) { + return '

'. t('A book by %author', array('%author' => $author)) .'

'; +} + +/** +* Theme the book license on the cover. +*/ +function theme_book_license($license) { + $output = '

'. t('License') .'

+

'. $license .'

'; + return $output; +} + +/** +* Implementation of hook_view() for a book cover. +*/ +function book_cover_view(&$node, $teaser = FALSE, $page = FALSE) { + $node = node_prepare($node, $teaser); + if (!empty($node->book_author)) { + $node->content['book_author'] = array( + '#value' => theme('book_author', $node->book_author), + '#weight' => -1, + ); + $node->teaser = '
'. $node->book_author .'
'. $node->teaser; + } + if (!empty($node->book_license)) { + $node->book_license = check_markup($node->book_license, $node->format, FALSE); + $node->content['book_license'] = array( + '#value' => theme('book_license', $node->book_license) , + '#weight' => 100, + ); + } + $node->content['book_toc'] = array( + '#value' => _book_cover_toc($node->bid), + '#weight' => 200, + ); + return $node; +} + +/** + * Creates the Table of contents printed on book cover $bid. + * Returns formatted html. + */ +function _book_cover_toc($bid) { + $result = db_query('SELECT n.nid, n.title, bp.parent, bp.weight FROM {node} n INNER JOIN {book_pages} bp ON n.vid = bp.vid WHERE n.status = 1 AND bp.bid = %d ORDER BY bp.weight, n.title', $bid); + + while ($node = db_fetch_object($result)) { + if (!$children[$node->parent]) { + $children[$node->parent] = array(); + } + $children[$node->parent][] = $node; + } + + return theme('book_toc', $children); +} + +/** + * Theme function returning nested list items. + */ +function theme_book_toc ($children) { + $toc = '

' . t('Table of contents') . '

'; + $toc .= build_book_toc($children); + $toc .= '

'; + return $toc; +} + +/** + * This is a helper function for theme_book_toc(). + */ +function build_book_toc($children, $nid = 0) { + if ($children[$nid]) { + $toc = '
    '; + foreach ($children[$nid] as $foo => $node) { + $toc .= '
  1. '. l($node->title , 'node/'.$node->nid). '
  2. '; + $toc .= build_book_toc($children,$node->nid); + } + $toc .= '
'; + } + return $toc; +} + +/** * Implementation of hook_nodeapi(). * * Appends book navigation to all nodes in the book. @@ -452,36 +821,51 @@ function book_nodeapi(&$node, $op, $teas switch ($op) { case 'view': if (!$teaser) { - $book = db_fetch_array(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid)); - if ($book) { - foreach ($book as $key => $value) { - $node->$key = $value; - } - - $path = book_location($node); - // Construct the breadcrumb: + if ($node->type == 'book_cover') { $node->breadcrumb = array(); // Overwrite the trail with a book trail. - foreach ($path as $level) { - $node->breadcrumb[] = array('path' => 'node/'. $level->nid, 'title' => $level->title); - } - $node->breadcrumb[] = array('path' => 'node/'. $node->nid); - - $node->content['book_navigation'] = array( - '#value' => theme('book_navigation', $node), - '#weight' => 100, - ); - - if ($page) { - menu_set_location($node->breadcrumb); + $node->breadcrumb[] = array ('path' => 'book', 'title' => t('books')); + $node->breadcrumb[] = array ('path' => 'node/'. $node->nid, 'title' => $node->title); + menu_set_location($node->breadcrumb); + } + else { + // check if it is a node added in the outline: + $book = db_fetch_array(db_query('SELECT * FROM {book_pages} WHERE vid = %d', $node->vid)); + if ($book) { + foreach ($book as $key => $value) { + $node->$key = $value; + } + + $path = book_location($node); + // Construct the breadcrumb: + $node->breadcrumb = array(); // Overwrite the trail with a book trail. + $node->breadcrumb[] = array ('path' => 'book', 'title' => t('books')); + if (!empty($node->bid)) { + $cover = db_fetch_object(db_query('SELECT n.title, n.nid FROM {book} AS b JOIN {node} AS n ON n.nid = b.nid WHERE b.bid = %d', $node->bid)); + $node->breadcrumb[] = array ('path' => 'node/'. $cover->nid, 'title' => $cover->title); + } + + foreach ($path as $level) { + $node->breadcrumb[] = array('path' => 'node/'. $level->nid, 'title' => $level->title); + } + $node->breadcrumb[] = array('path' => 'node/'. $node->nid); + + $node->content['book_navigation'] = array( + '#value' => theme('book_navigation', $node), + '#weight' => 100, + ); + + if ($page) { + menu_set_location($node->breadcrumb); + } } } } break; case 'delete revision': - db_query('DELETE FROM {book} WHERE vid = %d', $node->vid); + db_query('DELETE FROM {book_pages} WHERE vid = %d', $node->vid); break; case 'delete': - db_query('DELETE FROM {book} WHERE nid = %d', $node->nid); + db_query('DELETE FROM {book_pages} WHERE nid = %d', $node->nid); break; } } @@ -507,6 +891,10 @@ function theme_book_navigation($node) { drupal_add_link(array('rel' => 'up', 'href' => url('node/'. $node->parent))); $links .= l(t('up'), 'node/'. $node->parent, array('class' => 'page-up', 'title' => t('Go to parent page'))); } + else { + $cover_nid = db_result(db_query('SELECT nid FROM {book} WHERE bid = %d', $node->bid)); + $links .= l(t('up'), 'node/'. $cover_nid, array('class' => 'page-up', 'title' => t('Go to book cover'))); + } if ($next = book_next($node)) { drupal_add_link(array('rel' => 'next', 'href' => url('node/'. $next->nid))); $links .= l($next->title . t(' ›'), 'node/'. $next->nid, array('class' => 'page-next', 'title' => t('Go to next page'))); @@ -546,8 +934,9 @@ function book_toc_recurse($nid, $indent, /** * Returns an array of titles and nid entries of book pages in table of contents order. */ -function book_toc($exclude = 0) { - $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 ORDER BY b.weight, n.title')); +function book_toc($exclude = 0, $bid) { + $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, bp.parent, bp.weight FROM {node} n INNER JOIN {book_pages} bp ON n.vid = bp.vid WHERE n.status = 1 AND bp.bid = %d ORDER BY bp.weight, n.title'), $bid); + $children = array(); while ($node = db_fetch_object($result)) { @@ -558,10 +947,7 @@ function book_toc($exclude = 0) { } $toc = array(); - // If the user has permission to create new books, add the top-level book page to the menu; - if (user_access('create new books')) { - $toc[0] = '<'. t('top-level') .'>'; - } + $toc[0] = '<'. t('top-level') .'>'; $toc = book_toc_recurse(0, '', $toc, $children, $exclude); @@ -607,7 +993,7 @@ function book_tree_recurse($nid, $depth, * as a tree. */ function book_tree($parent = 0, $depth = 3, $unfold = array()) { - $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 ORDER BY b.weight, n.title')); + $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE n.status = 1 AND n.moderate = 0 ORDER BY b.weight, n.title')); while ($node = db_fetch_object($result)) { $list = isset($children[$node->parent]) ? $children[$node->parent] : array(); @@ -623,15 +1009,29 @@ function book_tree($parent = 0, $depth = /** * Menu callback; prints a listing of all books. */ -function book_render() { - $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = 0 AND n.status = 1 ORDER BY b.weight, n.title')); +function book_shelf() { + $result = db_query('SELECT b.bid, b.nid , n.title FROM {book} b JOIN {node} n ON n.nid = b.nid ORDER BY weight, title'); + $breadcrumb = array (l( t('Home'),'')); + drupal_set_breadcrumb($breadcrumb); + $output = ''; $books = array(); while ($node = db_fetch_object($result)) { $books[] = l($node->title, 'node/'. $node->nid); } + $output .= theme('item_list', $books, t('books')); + + // Add to those the top-level book pages without a book_cover. + $result = db_query('SELECT b.nid , n.title FROM {book_pages} b JOIN {node} n ON n.nid = b.nid WHERE parent = 0 AND bid = 0 ORDER BY weight, title'); + $books = array(); + while ($node = db_fetch_object($result)) { + $books[] = l($node->title, 'node/'. $node->nid); + } + if (count($books)) { + $output .= theme('item_list', $books, t('Independent top level book pages')); + } - return theme('item_list', $books); + return $output; } /** @@ -654,7 +1054,7 @@ function book_render() { */ function book_export($type = 'html', $nid = 0) { $type = drupal_strtolower($type); - $node_result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.nid = %d'), $nid); + $node_result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE n.nid = %d'), $nid); if (db_num_rows($node_result) > 0) { $node = db_fetch_object($node_result); } @@ -744,7 +1144,7 @@ function theme_book_export_html($title, * - the output generated in visiting each node */ function book_recurse($nid = 0, $depth = 1, $visit_pre, $visit_post) { - $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND n.nid = %d ORDER BY b.weight, n.title'), $nid); + $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE n.status = 1 AND n.nid = %d AND n.moderate = 0 ORDER BY b.weight, n.title'), $nid); while ($page = db_fetch_object($result)) { // Load the node: $node = node_load($page->nid); @@ -757,7 +1157,7 @@ function book_recurse($nid = 0, $depth = $output .= book_node_visitor_html_pre($node, $depth, $nid); } - $children = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE n.status = 1 AND b.parent = %d ORDER BY b.weight, n.title'), $node->nid); + $children = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE n.status = 1 AND b.parent = %d AND n.moderate = 0 ORDER BY b.weight, n.title'), $node->nid); while ($childpage = db_fetch_object($children)) { $childnode = node_load($childpage->nid); if ($childnode->nid != $node->nid) { @@ -825,7 +1225,16 @@ function _book_admin_table($nodes = arra ); foreach ($nodes as $node) { - $form = array_merge($form, _book_admin_table_tree($node, 0)); + if ($node->type == 'book_cover') { + $result = db_query('SELECT nid FROM {book_pages} WHERE parent = 0 AND bid = %d', $node->bid); + while ($chapters = db_fetch_object($result)) { + $chapter = node_load($chapters->nid); + $form = array_merge($form, _book_admin_table_tree($chapter, 0)); + } + } + else { + $form = array_merge($form, _book_admin_table_tree($node, 0)); + } } return $form; @@ -849,7 +1258,7 @@ function _book_admin_table_tree($node, $ ), ); - $children = db_query(db_rewrite_sql('SELECT n.nid, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = %d ORDER BY b.weight, n.title'), $node->nid); + $children = db_query(db_rewrite_sql('SELECT n.nid, b.weight FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE b.parent = %d ORDER BY b.weight, n.title'), $node->nid); while ($child = db_fetch_object($children)) { $form = array_merge($form, _book_admin_table_tree(node_load($child->nid), $depth + 1)); } @@ -902,7 +1311,7 @@ function book_admin_edit($nid) { * Menu callback; displays a listing of all orphaned book pages. */ function book_admin_orphan() { - $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, n.status, b.parent FROM {node} n INNER JOIN {book} b ON n.vid = b.vid')); + $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, n.status, b.parent FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid')); $pages = array(); while ($page = db_fetch_object($result)) { @@ -974,13 +1383,33 @@ function book_admin($nid = 0) { * Returns an administrative overview of all books. */ function book_admin_overview() { - $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book} b ON n.vid = b.vid WHERE b.parent = 0 ORDER BY b.weight, n.title')); + $output = ''; + + // List all book covers + $rows = array(); + $result = db_query(db_rewrite_sql('SELECT b.bid, b.nid , n.title FROM {book} b JOIN {node} n ON n.nid = b.nid ORDER BY weight, title')); while ($book = db_fetch_object($result)) { $rows[] = array(l($book->title, "node/$book->nid"), l(t('outline'), "admin/content/book/$book->nid")); } - $headers = array(t('Book'), t('Operations')); - return theme('table', $headers, $rows); + if (count($rows)) { + $headers = array(t('Book'), t('Operations')); + $output .= theme('table', $headers, $rows); + $output .= '
'; + } + + // List all independent top-level book pages. + $rows = array(); + $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.weight FROM {node} n INNER JOIN {book_pages} b ON n.vid = b.vid WHERE b.parent = 0 AND b.bid = 0 ORDER BY b.weight, n.title')); + while ($book = db_fetch_object($result)) { + $rows[] = array(l($book->title, "node/$book->nid"), l(t('outline'), "admin/content/book/$book->nid")); + } + if (count($rows)) { + $headers = array(t('Independent book pages'), t('Operations')); + $output .= theme('table', $headers, $rows); + } + + return $output; } /** @@ -1018,3 +1447,39 @@ function book_help($section) { } + +/** + * Prints a list of books in which the user can add pages. + */ +function _add_page_select_book() { + $books = array(); + + $result = db_query('SELECT b.bid, b.nid , n.title FROM {book} b JOIN {node} n ON n.nid = b.nid ORDER BY weight, title'); + while($book = db_fetch_object($result)) { + $books[] = l($book->title, 'node/add/book/bid/'.$book->bid); + } + + if (count($books)) { + $output = '

' . t('Either click here to create a book page that does not belong to any particular book or select below the book you wish to add the page to.', array('%url' => url('node/add/book/bid/0'))) .'

'; + $output .= theme('item_list', $books); + } + else { + drupal_goto('node/add/book/bid/0'); + } + return $output; +} + +/** + * Implementation of hook_form_alter() + */ +function book_form_alter($form_id, &$form) { + switch($form_id) { + case 'node_delete_confirm': + $node_type = db_result(db_query('SELECT type FROM {node} WHERE nid = %d ', $form['nid']['#value'])); + if ($node_type == 'book_cover') { + $form['book_cover'] = array( + '#value' => t('Note that all book pages belonging to this book (if any) will no longer be associated to any book. You may want to move remaining book pages within this book to another book, first. '), + ); + } + } +}