You are here

book_copy.module in Book Copy 6

Same filename and directory in other branches
  1. 7.2 book_copy.module
  2. 7 book_copy.module

File

book_copy.module
View source
<?php

// @file

/**
* implementation of hook_perm
*/
function book_copy_perm() {
  return array(
    'copy books',
    'view book history',
  );
}

/**
 * implementation of hook_menu
*/
function book_copy_menu() {
  $items['book_copy/copy/%node'] = array(
    'title' => 'Clone a book',
    'page callback' => 'book_copy_batch_copy',
    'page arguments' => array(
      2,
    ),
    'access callback' => 'book_copy_can_copy',
    'access arguments' => array(
      2,
    ),
    'type' => MENU_CALLBACK,
  );
  $items['book_copy/copy/%node/%node'] = array(
    'title' => 'Clone a book',
    'page callback' => 'book_copy_batch_copy',
    'page arguments' => array(
      2,
      3,
    ),
    'access callback' => 'book_copy_can_copy',
    'access arguments' => array(
      2,
      3,
    ),
    'type' => MENU_CALLBACK,
  );
  $items['book_copy/history/%node'] = array(
    'title' => 'Show a Books history',
    'page callback' => 'book_copy_show_history',
    'page arguments' => array(
      2,
    ),
    'access arguments' => array(
      'view book history',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['book_copy/explain/%node'] = array(
    'title' => 'Book Copy Status',
    'page callback' => 'book_copy_explain',
    'page arguments' => array(
      2,
      3,
    ),
    'access arguments' => array(
      'copy books',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implementatin of hook_link()
 */
function book_copy_link($type, $node = NULL, $teaser = FALSE) {
  global $user;
  $links = array();
  if ($type == 'node' && isset($node->book) && !$teaser) {
    if (user_access('view book history')) {
      $links['book_copy_history'] = array(
        'title' => t('Show Book History'),
        'href' => 'book_copy/history/' . $node->book['bid'],
      );
    }
    if (user_access('copy books')) {
      if (book_copy_can_copy($node->book['bid'])) {
        $links['book_copy_copy'] = array(
          'title' => t('Derive a Copy'),
          'href' => 'book_copy/copy/' . $node->book['bid'],
        );
      }
      elseif (variable_get('book_copy_always_display_copy_links', TRUE)) {
        $links['book_copy_copy'] = array(
          'title' => t('Derive a Copy'),
          'href' => 'book_copy/explain/' . $node->book['bid'],
        );
      }
      if ($node->book['bid'] != $node->nid && book_copy_can_copy($node->book['bid'], $node->nid)) {
        $links['book_copy_subtree'] = array(
          'title' => t('Copy subtree'),
          'href' => 'book_copy/copy/' . $node->book['bid'] . '/' . $node->nid,
        );
      }
      elseif (variable_get('book_copy_always_display_copy_links', TRUE)) {
        $links['book_copy_subtree'] = array(
          'title' => t('Copy subtree'),
          'href' => 'book_copy/explain/' . $node->book['bid'] . '/' . $node->nid,
        );
      }
    }
  }
  return $links;
}

/**
 * This function is intended to be called via drupals batch processing system and
 * will clone an entire or partial book.
 * @param object $book The book from which we are cloning content
 * @param int $subtree The nid to start cloning from if 0 all book pages will be copied
 *        if non-zero then only those nodes under the given nid in the tree hierarchy will
 *        be copied
 * @param array $context used to maintain state via batch code
*/
function book_copy_copy_book($book, $subtree = 0, &$context) {
  if (isset($book->book)) {
    $bid = $book->book['bid'];
    $book = node_load(array(
      'nid' => $bid,
    ));
    module_load_include('inc', 'clone', 'clone.pages');
    if (!isset($context['sandbox']['progress'])) {
      $nodes = book_copy_find_subtree($bid, $subtree);
      $context['sandbox']['newbid'] = 0;
      $context['sandbox']['max'] = count($nodes);
      $context['sandbox']['nodes'] = $nodes;
      $context['sandbox']['progress'] = 0;
      $context['sandbox']['finished'] = 0;
      $context['sandbox']['mlinks'] = array();
      $context['sandbox']['nidmap'] = array();
      $context['sandbox']['mlidmap'] = array();
    }
    if (!empty($context['sandbox']['nodes'])) {
      while ($context['sandbox']['progress'] < $context['sandbox']['max']) {
        $nid = $nodes[$context['sandbox']['progress']];
        $sql = 'SELECT ml.* FROM {menu_links} ml LEFT JOIN {book} b ON b.mlid = ml.mlid WHERE b.bid = %d AND b.nid = %d';
        $result = db_query($sql, $bid, $nid);
        $row = db_fetch_array($result);
        $plid = $row['plid'];
        $mlid = $row['mlid'];
        $context['sandbox']['progress']++;

        // copy each node keeping reference to new nid via old nid (this copies the old book structure)
        $context['sandbox']['nidmap'][$nid] = clone_node_save($nid);

        // clone_node_save() returns the nid of the new node.
        $node = node_load(array(
          'nid' => $context['sandbox']['nidmap'][$nid],
        ));
        $context['sandbox']['mlidmap'][$mlid] = $node->book['mlid'];
        if ($context['sandbox']['newbid'] == 0) {
          $context['sandbox']['newbid'] = $context['sandbox']['nidmap'][$nid];
          $message = $row['link_title'];

          // The function signature is: hook_book_copy_alter(&$node, $oldbid, $newbid);
          drupal_alter("book_copy", $node, $bid, $context['sandbox']['newbid']);

          // fix batch redirect, we didn't have the nid when batch_set() was called
          $batch =& batch_get();
          $data = book_copy_copy_redirection($context['sandbox']['newbid']);
          $batch['redirect'] = $data['url'];
        }
        if ($subtree != 0 || $node->nid != $context['sandbox']['newbid']) {

          // we need to set the bid for this node;
          $node->book['bid'] = $context['sandbox']['newbid'];
          node_save($node);
        }

        // fix lower level nested links
        if (isset($context['sandbox']['mlidmap'][$plid])) {
          $node->book['plid'] = $context['sandbox']['mlidmap'][$plid];
          menu_link_save($node->book);
        }
      }
    }
  }
  if ($context['sandbox']['progress'] < $context['sandbox']['max']) {
    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  }
  else {
    if ($context['sandbox']['progress'] != 0) {
      $context['finished'] = 1;
    }
  }
  if ($context['finished'] == 1) {
    foreach ($context['sandbox']['nidmap'] as $snid => $nid) {
      db_query('INSERT INTO {book_copy_history} (nid, bid, sbid, snid, copied) VALUES (%d, %d, %d, %d, %d)', $nid, $context['sandbox']['newbid'], $bid, $snid, time());
    }
  }
}

/**
 * This function defines our batch book copy process and starts it
 * Error checking ensures that we have a valid book, and generates a warning if 
 * the user tries to copy a non book
 *
 * @param object $book a node representing the book we want to copy
 * @param int $subtree The nid to start cloning from if 0 all book pages will be copied
 *        if non-zero then only those nodes under the given nid in the tree hierarchy will
 *        be copied
*/
function book_copy_batch_copy($book, $subtree = 0) {
  if (!empty($book->book['bid'])) {
    return drupal_get_form('book_copy_confirm_copy', $book, $subtree);
  }
  else {
    drupal_set_title(t('Error copying book'));
    if (!empty($book->title)) {
      drupal_set_message(t('%title is not a book', array(
        '%title' => $book->title,
      )), 'warning');
      drupal_goto('node/' . $book->nid);
    }
    else {
      drupal_set_message(t('Could not find book corresponding to nid: %nid', array(
        '%nid' => $bid,
      )), 'warning');
    }
  }
}

/**
 * Presents the user with a confirm dialog to avoid creating a mess of copied nodes.
 */
function book_copy_confirm_copy(&$form_state, $book, $subtree = 0) {
  if (!empty($subtree)) {
    $path = 'node/' . $subtree->nid;
  }
  else {
    $path = 'node/' . $book->nid;
  }
  $form['book'] = array(
    '#type' => 'value',
    '#value' => $book->nid,
  );
  if (!empty($subtree)) {
    $form['subtree'] = array(
      '#type' => 'value',
      '#value' => $subtree->nid,
    );
  }
  $form = confirm_form($form, t('Are you sure you want to copy %title', array(
    '%title' => $book->title,
  )), 'node/' . $book->nid, '');
  return $form;
}

/*
 *
 */
function book_copy_confirm_copy_submit($form, &$form_state) {
  $book = node_load(array(
    'nid' => $form_state['values']['book'],
  ));
  $subtree = $form_state['values']['subtree'];
  $batch = array(
    'title' => t('Copying %title', array(
      '%title' => $book->title,
    )),
    'operations' => array(
      array(
        'book_copy_copy_book',
        array(
          $book,
          $subtree,
        ),
      ),
    ),
  );
  batch_set($batch);
  batch_process();
}

/**
 * Given a bid/nid this function will generate any messages and the proper url the user should be 
 * redirected to
 * @param int $bid the book id to generate redirect & message for
*/
function book_copy_copy_redirection($bid) {
  $book = node_load(array(
    'nid' => $bid,
  ));
  $book->bookcopydata = array();
  $book->bookcopydata['message'] = t('Successfully cloned "%message", now viewing copy.', array(
    '%message' => $message,
  ));
  if (_book_outline_access($book)) {
    $book->bookcopydata['url'] = 'node/' . $bid . '/outline';
  }
  else {
    $book->bookcopydata['url'] = 'node/' . $bid;
  }

  // The function signature is: hook_book_copy_goto_alter(&$data);
  drupal_alter("book_copy_goto", $book);
  return $book->bookcopydata;
}

/**
 * Implementation of hook_clone_node_alter
*/
function book_copy_clone_node_alter(&$node, $original_node, $method) {
  if ($method == 'save-edit') {

    // remove "Clone of" text
    $node->title = str_replace('Clone of ', '', $node->title);
    if (isset($node->book)) {
      if ($node->book['plid'] == 0) {
        unset($node->book['nid']);
        $node->book['bid'] = 'new';
      }
    }
  }
}

/**
 * Initial function to render a books copy history
 *
 * @param object $book a node representing the book to show history for
*/
function book_copy_show_history($book) {
  drupal_set_title(t('Book History: @title', array(
    '@title' => $book->title,
  )));
  $ssbid = _book_copy_initial_source($book->book['bid']);
  $tree = _book_copy_get_book_history($ssbid);
  $output = _book_copy_render_tree($tree, $book->book['bid']);
  return $output;
}

/**
 * Function responsible for generating book history
 * @param array $tree formatted book hierarchy from _book_copy_get_book_history()
 * @param int $bid The nid of the book we are viewing history for
 */
function _book_copy_render_tree($tree, $bid) {
  foreach ($tree as $nid => $info) {
    if ($nid == $bid) {
      $class = 'class="messages"';
    }
    else {
      $class = '';
    }
    $output .= '<li>';
    $output .= '<span ' . $class . '>';
    $node = node_load($nid);
    if ($node) {
      $output .= l($node->title, 'node/' . $nid);
    }
    else {
      $output .= t('Book Deleted');
    }
    if (!empty($info['copied'])) {
      $output .= ' ' . t('Copied') . ': ' . format_date($info['copied']);
    }
    else {
      $output .= ' ' . t('Originating Source');
    }
    $output .= '</span>';
    if (!empty($info['children'])) {
      $output .= '<ul class="book-children">';
      foreach ($info['children'] as $key => $child) {
        $output .= _book_copy_render_tree(array(
          $key => $child,
        ), $bid);
      }
      $output .= '</ul>';
    }
    $output .= '</li>';
  }
  return $output;
}

/**
 * This function calls the recursive _book_copy_tree() to build
 * book history data
 * @param int $bid the bookid to build book history for
*/
function _book_copy_get_book_history($bid) {
  $return[$bid] = array();
  $return[$bid]['copied'] = 0;
  $return[$bid]['children'] = _book_copy_tree($bid);
  return $return;
}

/**
 * Recursive function used to build tree information for a book
 * @param int $bid The nid to build tree information for
*/
function _book_copy_tree($bid) {
  $result = db_query("SELECT * FROM {book_copy_history} WHERE sbid = %d", $bid);
  $children = array();
  while ($row = db_fetch_array($result)) {
    $children[$row['bid']] = array(
      'copied' => $row['copied'],
      'children' => _book_copy_tree($row['bid']),
    );
  }
  return $children;
}

/**
 * Recursive function to traverse up a book history tree to find the originating source 
 * of the book corresponding to $bid
 * @param int $bid The current bid to find source books for
*/
function _book_copy_initial_source($bid) {
  $result = db_query("SELECT * FROM {book_copy_history} WHERE bid = %d", $bid);
  $parents = array();
  while ($row = db_fetch_array($result)) {
    return _book_copy_initial_source($row['sbid']);
  }
  return $bid;
}

/**
 * Given a book id and an optional subtree (nid) this function returns a list of node ids
 * in book order that match the given criteria.
 */
function book_copy_find_subtree($bid, $subtree = 0) {
  $nodes = array();
  if (!empty($subtree)) {

    // only grab those nodes matching our sub-tree
    $mlid = db_result(db_query("SELECT ml.mlid FROM {menu_links} ml LEFT JOIN {book} b ON b.mlid = ml.mlid WHERE b.nid = %d", $subtree));
    $where = 'AND (ml.plid = %d OR ml.p1 = %d OR ml.p2 = %d OR ml.p3 = %d OR ml.p4 = %d OR ml.p5 = %d OR ml.p6 = %d OR ml.p7 = %d OR ml.p8 = %d OR ml.p9 = %d)';
    $args = array_fill(0, 10, $mlid);
    array_unshift($args, $bid);
  }
  else {
    $where = '';
    $args = array(
      $bid,
    );
  }
  $result = db_query("SELECT b.nid FROM {menu_links} ml LEFT JOIN {book} b ON b.mlid = ml.mlid WHERE b.bid = %d " . $where . " ORDER BY ml.depth, b.nid", $args);
  while ($nid = db_result($result)) {
    $nodes[] = $nid;
  }
  return $nodes;
}

/**
 * Given a book id and an optional subtree (nid) this function determines if the user
 * can copy the given book.
 *
 * @param $bid the book nid in question
 * @param $subtree optional if non-zero then the subtree pertainind to the nid supplied
 *  will be checked
 * @param $data an optional array where results will be saved. This will track why a user
 *  may not be able to copy a book or book sub-tree.
 *
 * @return TRUE if the user can copy the book FALSE otherwise
 */
function book_copy_can_copy($bid, $subtree = 0, &$data = array()) {
  if (is_object($bid)) {
    $bid = $bid->book['bid'];
  }
  if (is_object($subtree)) {
    $subtree = $subtree->nid;
  }
  $nodes = book_copy_find_subtree($bid, $subtree);
  $data = array();
  $data['headers'] = array(
    'node' => t('Book page'),
    'filter' => t('Input format'),
    'create' => t('Create node type'),
  );
  $can_copy = TRUE;
  if (user_access('copy books')) {
    if (!empty($nodes)) {
      foreach ($nodes as $nid) {
        $node = node_load(array(
          'nid' => $nid,
        ));
        $data[$nid]['node'] = l($node->title, 'node/' . $nid);
        if (!filter_access($node->format)) {
          $can_copy = FALSE;
          $data[$nid]['filter']['status'] = FALSE;
          $data[$nid]['filter']['message'] = $node->format;
        }
        else {
          $data[$nid]['filter']['status'] = TRUE;
        }
        if (!node_access('create', $node->type)) {
          $can_copy = FALSE;
          $data[$nid]['create']['status'] = FALSE;
          $data[$nid]['create']['message'] = $node->type;
        }
        else {
          $data[$nid]['create']['status'] = TRUE;
        }
      }
      return $can_copy;
    }
  }
  else {
    return FALSE;
  }
}
function book_copy_explain($book, $subtree = 0) {
  $table = array();
  $output = '';
  if (book_copy_can_copy($book->book['bid'], $subtree, $data)) {
    if ($subtree != 0) {
      $output = t('You can copy this sub-section of the book.');
    }
    else {
      $output = t('You can copy this book.');
    }
  }
  else {
    if ($subtree != 0) {
      $output = t('You cannot copy this sub-section of the book. ');
    }
    else {
      $output = t('You cannot copy this book. ');
    }
    $output .= t('In order to copy this book the following issue(s) must be addressed. Please contact your site administrator for help.');
  }
  $table['headers'] = $data['headers'];
  unset($data['headers']);
  $table['rows'] = array();
  foreach ($data as $nid => $column) {
    $row = array();
    $hide = TRUE;
    foreach ($table['headers'] as $key => $header) {
      if ($column[$key]['status'] === FALSE) {
        $row['data'][] = t('N(%message)', array(
          '%message' => $column[$key]['message'],
        ));
        $hide = FALSE;
      }
      elseif ($column[$key]['status'] === TRUE) {
        $row['data'][] = t('Y');
      }
      else {
        $row['data'][] = $column[$key];
      }
    }
    if (!$hide) {
      $table['rows'][] = $row;
    }
  }
  if (!empty($table['rows'])) {
    $output .= theme_table($table['headers'], $table['rows']);
  }
  return $output;
}

Functions

Namesort descending Description
book_copy_batch_copy This function defines our batch book copy process and starts it Error checking ensures that we have a valid book, and generates a warning if the user tries to copy a non book
book_copy_can_copy Given a book id and an optional subtree (nid) this function determines if the user can copy the given book.
book_copy_clone_node_alter Implementation of hook_clone_node_alter
book_copy_confirm_copy Presents the user with a confirm dialog to avoid creating a mess of copied nodes.
book_copy_confirm_copy_submit
book_copy_copy_book This function is intended to be called via drupals batch processing system and will clone an entire or partial book.
book_copy_copy_redirection Given a bid/nid this function will generate any messages and the proper url the user should be redirected to
book_copy_explain
book_copy_find_subtree Given a book id and an optional subtree (nid) this function returns a list of node ids in book order that match the given criteria.
book_copy_link Implementatin of hook_link()
book_copy_menu implementation of hook_menu
book_copy_perm implementation of hook_perm
book_copy_show_history Initial function to render a books copy history
_book_copy_get_book_history This function calls the recursive _book_copy_tree() to build book history data
_book_copy_initial_source Recursive function to traverse up a book history tree to find the originating source of the book corresponding to $bid
_book_copy_render_tree Function responsible for generating book history
_book_copy_tree Recursive function used to build tree information for a book