You are here

book_copy.module in Book Copy 7

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

File

book_copy.module
View source
<?php

// @file
// Book Copy allows you to replicate an entire or part of a book outline

/**
 * Implements hook_permission().
 */
function book_copy_permission() {
  return array(
    'copy books' => array(
      'title' => t('copy books'),
      'description' => t('Give user the ability to copy books'),
    ),
    'view book history' => array(
      'title' => t('view book history'),
      'description' => t('View the history of book copying'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function book_copy_menu() {
  $items['book_copy/copy/%node'] = array(
    'title' => 'Clone book',
    'page callback' => 'book_copy_copy_book',
    'page arguments' => array(
      2,
    ),
    'access arguments' => array(
      'copy books',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['book_copy/copy/%node/%node'] = array(
    'title' => 'Clone book',
    'page callback' => 'book_copy_copy_book',
    'page arguments' => array(
      2,
      3,
    ),
    'access arguments' => array(
      'copy books',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['book_copy/history/%node'] = array(
    'title' => 'Show Book history',
    'page callback' => 'book_copy_show_history',
    'page arguments' => array(
      2,
    ),
    'access arguments' => array(
      'view book history',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implementatin of hook_node_view()
 */
function book_copy_node_view($node, $view_mode) {
  global $user;
  $links = array();

  //ensure that we have child pages
  if (isset($node->book['depth'])) {
    if ($view_mode == 'full' && node_is_page($node)) {
      $child_type = variable_get('book_child_type', 'book');

      // makre sure this is a valid piece of content for nesting
      if ((user_access('add content to books') || user_access('administer book outlines')) && node_access('create', $child_type) && $node->status == 1 && $node->book['depth'] < MENU_MAX_DEPTH) {
        $links['book_add_child'] = array(
          'title' => t('Add child page'),
          'href' => 'node/add/' . str_replace('_', '-', $child_type),
          'query' => array(
            'parent' => $node->book['mlid'],
          ),
        );
      }

      // link to view history if allowed
      if (user_access('view book history')) {
        $links['book_copy_history'] = array(
          'title' => t('Show Book History'),
          'href' => 'book_copy/history/' . $node->book['bid'],
        );
      }

      // link to copy book if allowed
      if (user_access('copy books')) {
        $links['book_copy_copy'] = array(
          'title' => t('Derive a Copy'),
          'href' => 'book_copy/copy/' . $node->book['bid'],
        );

        // link to copy sub-tree / book child pages if allowed
        if ($node->book['bid'] != $node->nid) {
          $links['book_copy_subtree'] = array(
            'title' => t('Copy subtree'),
            'href' => 'book_copy/copy/' . $node->book['bid'] . '/' . $node->nid,
          );
        }
      }

      // keep in the same container
      if (user_access('access printer-friendly version')) {
        $links['book_printer'] = array(
          'title' => t('Printer-friendly version'),
          'href' => 'book/export/html/' . $node->nid,
          'attributes' => array(
            'title' => t('Show a printer-friendly version of this book page and its sub-pages.'),
          ),
        );
      }
    }
  }

  // theme the links if there are any allowed
  if (!empty($links)) {
    $node->content['links']['book'] = array(
      '#theme' => 'links__node__book',
      '#links' => $links,
      '#attributes' => array(
        'class' => array(
          'links',
          'inline',
        ),
      ),
    );
  }
  return $node;
}

/**
 * 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) {
  $newbid = 0;

  // make sure this is part of a book
  if (isset($book->book)) {
    $bid = $book->book['bid'];
    $book = node_load($bid);
    $args = array(
      'bid' => $bid,
    );
    if (is_object($subtree)) {
      $mlid_res = db_query("SELECT ml.mlid FROM {menu_links} ml LEFT JOIN {book} b ON b.mlid = ml.mlid WHERE b.nid = :subtree", array(
        'subtree' => $subtree->nid,
      ));
      $mlid = $mlid_res
        ->fetchAll();
      $where = 'AND (ml.plid = :mlid OR ml.p1 = :mlid OR ml.p2 = :mlid OR ml.p3 = :mlid OR ml.p4 = :mlid OR ml.p5 = :mlid OR ml.p6 = :mlid OR ml.p7 = :mlid OR ml.p8 = :mlid OR ml.p9 = :mlid)';
      $args['mlid'] = $mlid[0]->mlid;
    }
    else {
      $where = '';
    }
    module_load_include('inc', 'clone', 'clone.pages');
    $result = db_query("SELECT ml.menu_name, ml.mlid, ml.plid, ml.link_path, ml.router_path, ml.link_title, ml.module, b.nid FROM {menu_links} ml LEFT JOIN {book} b ON b.mlid = ml.mlid WHERE b.bid = :bid " . $where . " ORDER BY ml.depth", $args);
    if ($result) {
      $mlinks = array();
      $nidmap = array();
      $mlidmap = array();
      $result = $result
        ->fetchAll();

      // loop through results
      foreach ($result as $row) {
        $plid = $row->plid;
        $mlid = $row->mlid;

        // clone item and save it into the map
        $nidmap[$row->nid] = clone_node_save($row->nid);

        // load the new node from the map
        $node = node_load($nidmap[$row->nid]);
        $mlidmap[$mlid] = $node->book['mlid'];

        // make sure this isn't the book root
        if ($newbid == 0) {
          $newbid = $nidmap[$row->nid];
          $message = $row->link_title;

          // book_copy alter to allow for modification during operation
          drupal_alter("book_copy", $node, $bid, $newbid);
        }
        if (is_object($subtree) || $node->nid != $newbid) {
          $node->book['bid'] = $newbid;
          node_save($node);
        }

        // save the menu link if the map is set
        if (isset($mlidmap[$plid])) {
          $node->book['plid'] = $mlidmap[$plid];
          menu_link_save($node->book);
        }
      }
      foreach ($nidmap as $snid => $nid) {

        // TODO use drupal_write_record function
        db_query('INSERT INTO {book_copy_history} (nid, bid, sbid, snid, copied) VALUES (:nid, :bid, :sbid, :snid, :copied)', array(
          'nid' => $nid,
          'bid' => $newbid,
          'sbid' => $bid,
          'snid' => $snid,
          'copied' => time(),
        ));
      }
      $book = node_load($newbid);
      $book->bookcopydata = array();
      $message = $book->title;
      $book->bookcopydata['message'] = t('Successfully cloned "%message", now viewing copy.', array(
        '%message' => $message,
      ));

      // ensure access to this outline
      if (_book_outline_access($book)) {
        $book->bookcopydata['url'] = 'node/' . $newbid . '/outline';
      }
      else {
        $book->bookcopydata['url'] = 'node/' . $newbid;
      }

      // allow for modification of the goto after duplication
      drupal_alter("book_copy_goto", $book);
      drupal_set_message($book->bookcopydata['message']);

      // go to the book copy address or the node itself based on permission
      drupal_goto($book->bookcopydata['url']);
    }
  }
}

/**
 * Implements hook_clone_node_alter().
 */
function book_copy_clone_node_alter(&$node, $original_node, $method) {

  // make sure this is part of a book
  if (!empty($node->book)) {
    if ($node->book['plid'] != 0) {

      // remove "Clone of" text
      $node->title = drupal_substr($node->title, 9);
    }
    else {
      unset($node->book['nid']);
      $node->book['bid'] = 'new';
    }
  }
}

/**
 * This function while being very small is doing quite a bit, describe what it's doing
 * This is also a page callback for 'book_copy/history/%node' so indicate it's a Callback function in the commment
 */
function book_copy_show_history($book) {
  drupal_set_title(t('Book History: @title', array(
    '@title' => $book->title,
  )), PASS_THROUGH);
  $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;
}

/**
 * Typically this is going to be a theme_book_copy_render_tree function, that way 
 * you call it with theme('book_copy_render_tree') and would allow others to overload it
 * via a theme specific implementation if they wanted to
 */
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');
    }

    // add copy data to output if it is set
    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;
}

/**
 * Render the data tree for display on the book history page.
 */
function _book_copy_get_book_history($bid) {
  $return[$bid] = array();
  $return[$bid]['copied'] = 0;
  $return[$bid]['children'] = _book_copy_tree($bid);
  return $return;
}

/**
 * Copy the data tree that's been passed in.
 */
function _book_copy_tree($bid) {
  $result = db_select('book_copy_history', 'bch')
    ->fields('bch', array())
    ->condition('bch.sbid', $bid)
    ->execute();
  $children = array();
  foreach ($result as $row) {
    $bid = $row->bid;
    $children[$bid] = array(
      'copied' => $row->copied,
      'children' => _book_copy_tree($row->bid),
    );
  }
  return $children;
}

/**
 * Go back through to the initial source.
 */
function _book_copy_initial_source($bid) {
  $result = db_select('book_copy_history', 'bch')
    ->fields('bch', array())
    ->condition('bch.bid', $bid)
    ->execute();
  foreach ($result as $row) {
    return _book_copy_initial_source($row->sbid);
  }
  return $bid;
}

/**
 * Implements hook_outline_designer_form_overlay().
 */
function book_copy_outline_designer_form_overlay() {
  $book_copy['od_book_copy_title'] = array(
    '#title' => t('Title format'),
    '#id' => 'od_book_copy_title',
    '#type' => 'textfield',
    '#required' => TRUE,
    '#size' => 20,
    '#description' => t('Tokens: @title = content title'),
    '#weight' => 2,
  );
  $output = '<div id="od_book_copy" class="od_uiscreen">
    ' . drupal_render($book_copy) . '
  </div>';
  return $output;
}

/**
 * Implements hook_outline_designer_operations_alter().
 */
function book_copy_outline_designer_operations_alter(&$ops, $type) {

  // seems silly but this way other hooked in actions are last
  switch ($type) {
    case 'book':
      $icon_path = drupal_get_path('module', 'book_copy') . '/images/';
      $book_copy_ops = array(
        'book_copy' => array(
          'title' => t('Copy content'),
          'icon' => $icon_path . 'book_copy.png',
          'callback' => 'book_copy_book_process_book_copy',
        ),
      );
      $ops = array_merge($ops, $book_copy_ops);
      break;
  }
}

/**
 * Callback for book_copy ajax call from outline designer.
 */
function book_copy_book_process_book_copy($nid, $clone_title) {

  // TODO: this needs to implement the internal book duplication functions
  global $user;

  // need to account for the 3 weird characters in URLs
  $clone_title = str_replace("@2@F@", '/', $clone_title);
  $clone_title = str_replace("@2@3@", '#', $clone_title);
  $clone_title = str_replace("@2@B@", '+', $clone_title);
  $node = node_load($nid);
  $orig_node = $node;
  $node->nid = NULL;
  $node->vid = NULL;
  if (isset($node->uuid)) {
    $node->uuid = NULL;
  }
  $node->created = NULL;
  $node->book['mlid'] = NULL;
  $node->book['has_children'] = 0;
  $node->uid = $user->uid;

  // swap out the title
  $new_title = str_replace('@title', $node->title, $clone_title);
  $node->title = $new_title;
  if (node_access('create', $node) && node_access('view', $orig_node)) {
    node_save($node);
    drupal_set_message(t('Content copied from %title (%nid).', array(
      '%title' => $node->title,
      '%nid' => $nid,
    )));
    return 1;
  }
  else {
    drupal_set_message(t('Outline copy denied because of permissions.'));
    return 0;
  }
}

/**
 * Implements hook_outline_designer_ops_js().
 */
function book_copy_outline_designer_ops_js($ajax_path, $nid = NULL) {
  drupal_add_js(drupal_get_path('module', 'book_copy') . '/js/book_copy_ops.js', array(
    'scope' => 'footer',
  ));
}

Functions

Namesort descending Description
book_copy_book_process_book_copy Callback for book_copy ajax call from outline designer.
book_copy_clone_node_alter Implements hook_clone_node_alter().
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_menu Implements hook_menu().
book_copy_node_view Implementatin of hook_node_view()
book_copy_outline_designer_form_overlay Implements hook_outline_designer_form_overlay().
book_copy_outline_designer_operations_alter Implements hook_outline_designer_operations_alter().
book_copy_outline_designer_ops_js Implements hook_outline_designer_ops_js().
book_copy_permission Implements hook_permission().
book_copy_show_history This function while being very small is doing quite a bit, describe what it's doing This is also a page callback for 'book_copy/history/%node' so indicate it's a Callback function in the commment
_book_copy_get_book_history Render the data tree for display on the book history page.
_book_copy_initial_source Go back through to the initial source.
_book_copy_render_tree Typically this is going to be a theme_book_copy_render_tree function, that way you call it with theme('book_copy_render_tree') and would allow others to overload it via a theme specific implementation if they wanted to
_book_copy_tree Copy the data tree that's been passed in.