You are here

epub.module in Epub 6

Same filename and directory in other branches
  1. 8 epub.module
  2. 7 epub.module

Provide ePub content type and enable the creation of ePub files from book contents.

File

epub.module
View source
<?php

/**
 * @file
 * Provide ePub content type and enable the creation of ePub files
 * from book contents.
 */

///////////////////////////////////// HOOKS ///////////////////////////////////

/**
 * Implementation of hook_help().
 */
function epub_help($path, $arg) {
  switch ($path) {
    case 'admin/help#epub':
      return '<p>' . t('The ePub module allows administrators to associate ePub content with book outlines.') . '</p><p>' . t('By enabling this module, you will allow the user to download the ePub conversion of your book contents.<br/>') . t('EPub settings can be found at <a href="@epub_config_page">ePub Settings</a> page, while the administration panel is found at <a href="@epub_admin_page">ePub List</a> page.<br/>', array(
        '@epub_config_page' => url('admin/content/epub/settings'),
        '@epub_admin_page' => url('admin/content/epub'),
      )) . t('In order to create or download an ePub, visit a book page where you can find the proper ePub tab in the menu.') . '</p>';
    case 'admin/content/epub':
      return '<p>' . t('The ePub module is suited for exporting book contents to ePub file format.') . '</p>';
  }
}

/**
 * Implementation of hook_perm().
 */
function epub_perm() {
  return array(
    'administer epub',
    'access epub',
  );
}

/**
 * Implementation of hook_access().
 */
function epub_access($op, $node, $account) {
  switch ($op) {
    case 'create':
    case 'delete':
    case 'update':
      return user_access('administer epub', $account);
    case 'view':
      return user_access('access epub', $account);
    default:
      return FALSE;
  }
}

/**
 * Implementation of hook_menu().
 */
function epub_menu() {
  $items = array();
  $items['epub/book_list_autocomplete'] = array(
    'title' => 'Book List Autocomplete Callback',
    'page callback' => 'epub_list_books_autocomplete',
    'access arguments' => array(
      'administer epub',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/content/epub'] = array(
    'title' => 'ePubs',
    'description' => "Manage your site' s ePubs",
    'page callback' => 'epub_admin_overview',
    'access arguments' => array(
      'administer epub',
    ),
    'file' => 'epub.admin.inc',
  );
  $items['admin/content/epub/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/content/epub/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'epub_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'epub.admin.inc',
  );
  $items['node/%/epub'] = array(
    'title callback' => 'epub_read_title_callback',
    'title arguments' => array(
      1,
    ),
    'description' => 'Read an ePub',
    'page callback' => 'epub_read_page_callback',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'epub_read_access_callback',
    'access arguments' => array(
      1,
      'access epub',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 10,
  );
  return $items;
}

/**
 * Implementation of hook_node_info().
 */
function epub_node_info() {
  return array(
    'epub' => array(
      'name' => t('ePub'),
      'description' => 'An ePub is a special format used in ebook readers to render books',
      'module' => 'epub',
      'has_title' => TRUE,
      'has_body' => TRUE,
      'body_label' => t('Description'),
    ),
  );
}

/**
 * Implementation of hook_nodeapi().
 */
function epub_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
    case 'delete_revision':
      db_query(db_rewrite_sql('DELETE FROM {epub} WHERE vid = %d'), $node->vid);
      break;
  }
}

/**
 * Implementation of hook_form().
 */
function epub_form(&$node) {
  $type = node_get_types('type', $node);
  $form = array();
  if ($type->has_title) {
    $form['title'] = array(
      '#type' => 'textfield',
      '#title' => check_plain($type->title_label),
      '#required' => TRUE,
      '#default_value' => $node->title,
      '#weight' => -5,
    );
  }
  if ($type->has_body) {
    $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
  }
  _epub_add_form_elements($form, $node);
  return $form;
}

/**
 * Implementation of hook_insert().
 */
function epub_insert($node) {
  $result = db_query_range(db_rewrite_sql("SELECT b.nid FROM {node} AS n\n      INNER JOIN {book} AS b ON b.nid = n.nid\n      WHERE b.nid = b.bid AND n.title = '%s'"), $node->book_outline, 0, 1);
  $book = db_fetch_object($result);
  $node->bid = $book->bid;
  var_dump($node);
  exit;

  /*
    if (!drupal_write_record('epub', $node)) {
      node_delete($node->nid);
      drupal_set_message(t('Error occurred during the creation of the ePub.'), 'error');
      //drupal_goto('node/add/epub');
    }*/
}

/**
 * Implementation of hook_update().
 */
function epub_update($node) {
  if ($node->revision) {
    epub_insert($node);
  }
  else {
    $result = db_query_range(db_rewrite_sql("SELECT b.nid FROM {node} AS n\n        INNER JOIN {book} AS b ON b.nid = n.nid\n        WHERE b.nid = b.bid AND n.title = '%s'"), $node->book_outline, 0, 1);
    $book = db_fetch_object($result);
    $result = db_query(db_rewrite_sql("UPDATE {epub} SET\n          bid = %d,\n          author_name = '%s',\n          language_code = '%s',\n          identifier = '%s',\n          identifier_type = '%s',\n          publisher_name = '%s',\n          publisher_site = '%s',\n          creation_date = '%s',\n          rights = '%s',\n          source_url = '%s'\n        WHERE vid = %d"), $book->nid, $node->author_name, $node->language_code, $node->identifier, $node->identifier_type, $node->publisher_name, $node->publisher_site, $node->creation_date, $node->rights, $node->source_url, $node->vid);
    if (!$result) {
      drupal_set_message(t('Error occurred while updating the ePub.'), 'error');

      //drupal_goto("node/$node->vid/epub");
    }
  }
}

/**
 * Implementation of hook_delete().
 */
function epub_delete($node) {
  $result = db_query(db_rewrite_sql('DELETE FROM {epub} WHERE nid = %d'), $node->nid);

  //  node_delete($node->nid);
  if (!$result) {
    drupal_set_message(t('Error occurred while removing the ePub.'), 'error');
  }

  //drupal_goto('admin/content/epub');
}

/**
 * Implementation of hook_load().
 */
function epub_load($node) {
  return db_fetch_object(db_query(db_rewrite_sql('SELECT n.title AS book_outline, e.bid, e.author_name, e.language_code, e.identifier, e.identifier_type, e.publisher_name,
        e.publisher_site, e.creation_date, e.rights, e.source_url
        FROM {epub} AS e
        LEFT JOIN {node} AS n ON e.bid = n.nid
        WHERE e.vid = %d'), $node->vid));
}

/**
 * Implementation of hook_view().
 */
function epub_view($node, $teaser = FALSE, $page = FALSE) {
  $node = node_prepare($node, $teaser);
  $node->content['epub'] = array(
    '#value' => theme('epub_info', l($node->book_outline, 'node/' . $node->bid), check_plain($node->author_name), check_plain($node->language_code), check_plain($node->identifier), check_plain($node->identifier_type), check_plain($node->creation_date), check_plain($node->publisher_name), l($node->publisher_site, $node->publisher_site), check_plain($node->rights), l($node->source_url, $node->source_url)),
    '#weight' => 1,
  );
  return $node;
}

/**
 * Implementation of hook_theme().
 */
function epub_theme() {
  return array(
    'epub_info' => array(
      'template' => 'epub_info',
      'arguments' => array(
        'book_outline' => NULL,
        'author_name' => NULL,
        'language_code' => NULL,
        'identifier' => NULL,
        'identifier_type' => NULL,
        'creation_date' => NULL,
        'publisher_name' => NULL,
        'publisher_site' => NULL,
        'rights' => NULL,
        'source_url' => NULL,
      ),
    ),
  );
}

/**
 * Implementation of hook_autocomplete().
 */
function epub_list_books_autocomplete($arg) {
  $matches = array();
  $result = db_query_range(db_rewrite_sql("SELECT n.title FROM {node} AS n\n      INNER JOIN {book} AS b ON b.nid = n.nid\n      WHERE b.nid = b.bid AND n.title LIKE '%%%s%%'"), $arg, 0, 10);
  while ($row = db_fetch_object($result)) {
    $matches[$row->title] = check_plain($row->title);
  }
  print drupal_to_js($matches);
}

/**
 * Implementation of hook_views_api().
 */
function epub_views_api() {
  return array(
    'api' => 2.0,
  );
}

//////////////////////////////// MENU CALLBACKS ///////////////////////////////

/**
 * Determine correct menu title
 *
 * @param $nid
 *   An integer containing node id.
 *
 * @return
 *   A string containing the title for menu tab of a book node.
 */
function epub_read_title_callback($nid) {
  $node = node_load($nid);
  if ($node->type === 'book') {
    return _epub_links_book($nid) ? t('Download ePub') : t('Add ePub');
  }
  elseif ($node->type === 'epub') {
    return _epub_links_book($node->bid) ? t('Download ePub') : t('Add ePub');
  }
}

/**
 * Determine whether the user has to create the ePub or can download it.
 *
 * @param $nid
 *   An integer containing node id.
 *
 * @return
 *   If the ePub exists, it send the corresponding file to the user
 *   else it redirects to the 'add ePub' page.
 */
function epub_read_page_callback($nid) {
  $node = node_load($nid);
  if ($node->type === 'book') {
    _epub_links_book($nid) ? epub_create_file($nid) : drupal_goto('node/add/epub');
  }
  elseif ($node->type === 'epub') {
    if (_epub_links_book($node->bid)) {
      epub_create_file($node->bid);
    }
    else {
      drupal_set_message(t('You have to link a book outline before you can download its ePub'), 'warning');
      drupal_goto("node/{$nid}/epub");
    }
  }
}

/**
 * Determine whether the user can download an ePub or not.
 *
 * @param $nid
 *   An integer containing node id.
 * @param $perm
 *  The permission, such as "administer nodes", being checked for.
 *
 * @return
 *   A boolean value telling if the user has the rights to access the content.
 */
function epub_read_access_callback($nid, $perm) {
  $node = node_load(array(
    'nid' => $nid,
  ));
  return ($node->type === 'book' || $node->type === 'epub' && _epub_links_book($node->bid)) && user_access($perm);
}

/////////////////////////////////// EPUB API //////////////////////////////////

/**
 * Generate an ePub file from a book.
 *
 * @param $nid
 *   An integer containing book node id.
 *
 * @return
 *   If a book structure exists, send the ePub file to the user
 *   else set an error message and redirect to the node page.
 */
function epub_create_file($nid) {
  $epub_library_path = 'sites/all/libraries/epub';
  require_once "{$epub_library_path}/EPubChapterSplitter.php";
  require_once "{$epub_library_path}/EPub.php";
  $epub_nid = _epub_links_book($nid);
  if (!$epub_nid) {
    drupal_set_message(t('Error during the creation of the ePub'), 'error');
    drupal_goto("node/{$nid}");
  }
  $epub_node = node_load(array(
    'nid' => $epub_nid,
  ));
  $book_node = node_load(array(
    'nid' => $nid,
  ));
  $book_tree = _epub_get_book_tree($book_node);
  if (empty($book_tree)) {
    drupal_set_message(t('Error during the creation of the ePub'), 'error');
    drupal_goto("node/{$nid}");
  }
  $epub = new EPub();

  // Title and Identifier are mandatory!
  $epub
    ->setTitle($epub_node->title);

  // Could also be the ISBN, ISSN, UUID or others.
  $epub
    ->setIdentifier($epub_node->identifier ? $epub_node->identifier : "1234", "URI");

  // Language is mandatory, but EPub defaults to "en".

  //Use RFC3066 Language codes, such as "en", "da", "fr" etc.
  $epub
    ->setLanguage($epub_node->language_code);
  $epub
    ->setDescription($epub_node->body);
  $epub
    ->setAuthor($epub_node->author_name, $epub_node->author_name);
  $epub
    ->setPublisher($epub_node->publisher_name, $epub_node->publisher_site);

  // Strictly not needed as the book date defaults to time().
  $epub
    ->setDate(date('U', $epub_node->creation_date));

  // As this is generated, this _could_ contain the name or licence information
  // of the user who purchased the book, if needed. If this is used that way,
  // the identifier must also be made unique for the book.
  $epub
    ->setRights($epub_node->rights);
  $epub
    ->setSourceURL($epub_node->source_url);
  _epub_traverse_book_tree($book_tree, $epub);
  $epub
    ->setIgnoreEmptyBuffer(true);
  $epub
    ->finalize();

  // Finalize the book, and build the archive.
  $data = $epub
    ->getBook();
  $headers = array(
    'Pragma: public',
    'Last-Modified: ' . date('r', time()),
    'Expires: 0',
    'Accept-Ranges: bytes',
    'Connection: close',
    'Content-Type: application/epub+zip',
    'Content-Disposition: attachment; filename="' . $epub_node->title . '.epub";',
    'Content-Transfer-Encoding: binary',
    'Content-Length: "' . drupal_strlen($data),
  );
  _epub_file_transfer($data, $headers);
}

/////////////////////////////////// HELPERS ///////////////////////////////////

/**
 * Check if ePub links a book
 *
 * @param $nid
 *   An integer containing node id.
 *
 * @return
 *   On success, the ePub nid;
 *   On failure, FALSE.
 */
function _epub_links_book($nid) {
  $book = db_fetch_object(db_query(db_rewrite_sql("SELECT nid FROM {epub} WHERE bid = %d"), $nid));
  return $book->nid ? $book->nid : FALSE;
}

/**
 * Build the common elements of the epub form for the node forms.
 *
 * @param &$form
 *   The form structure to be altered
 * @param $node
 *   The node containing ePub information
 */
function _epub_add_form_elements(&$form, $node) {
  $form['epub'] = array(
    '#type' => 'fieldset',
    '#title' => t('ePub settings'),
    '#weight' => 5,
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['epub']['book_outline'] = array(
    '#type' => 'textfield',
    '#title' => t('Book'),
    '#description' => t("ePub file' s cover page, root of the book."),
    '#required' => TRUE,
    '#autocomplete_path' => 'epub/book_list_autocomplete',
    '#default_value' => $node->book_outline,
  );
  $form['epub']['author_name'] = array(
    '#type' => 'textfield',
    '#title' => t('Author'),
    '#description' => t('Who wrote this book'),
    '#required' => FALSE,
    '#default_value' => $node->author_name,
  );
  $form['epub']['language_code'] = array(
    '#type' => 'iso_639',
    '#title' => t('Language'),
    '#description' => t('RFC3066 Language codes, such as "en", "da", "fr", ...'),
    '#required' => TRUE,
    '#default_value' => $node->language_code ? $node->language_code : 'en',
  );
  $form['epub']['identifier'] = array(
    '#type' => 'textfield',
    '#title' => t('Identifier'),
    '#description' => t("The ePub' s ISBN number, preferred for published books, or a UUID."),
    '#required' => FALSE,
    '#default_value' => $node->identifier,
  );
  $form['epub']['identifier_type'] = array(
    '#type' => 'textfield',
    '#title' => t('Identifier Type'),
    '#description' => t("The ePub' s identifier type, ie: ISBN, ISSN, UUID, ..."),
    '#required' => FALSE,
    '#default_value' => $node->identifier_type,
  );
  $form['epub']['publisher_name'] = array(
    '#type' => 'textfield',
    '#title' => t("Publisher' s Name"),
    '#description' => t("Publisher' s name."),
    '#required' => FALSE,
    '#default_value' => $node->publisher_name,
  );
  $form['epub']['publisher_site'] = array(
    '#type' => 'textfield',
    '#title' => t("Publisher' s Site"),
    '#description' => t("Publisher' s site."),
    '#required' => FALSE,
    '#default_value' => $node->publisher_site,
  );
  $form['epub']['rights'] = array(
    '#type' => 'textfield',
    '#title' => t('Rights'),
    '#description' => t('Copyright and license information specific for the ePub.'),
    '#required' => FALSE,
    '#default_value' => $node->rights,
  );
  $form['epub']['creation_date'] = array(
    '#type' => 'textfield',
    '#title' => t('Creation date'),
    '#default_value' => date('Y-m-d H:i:s'),
    '#description' => t('Creation date(YYYY-MM-DD HH:MM:SS format).'),
    '#required' => FALSE,
    '#default_value' => isset($node->creation_date) ? $node->creation_date : date('Y-m-d H:i:s'),
  );
  $form['epub']['source_url'] = array(
    '#type' => 'textfield',
    '#title' => t('Source URL'),
    '#description' => t('URL of the site in which the ePub is available.'),
    '#required' => FALSE,
    '#default_value' => $node->source_url,
  );
}

/**
 * Return a structured book hierarchy.
 *
 * @param $node
 *   A node object.
 * @param &$rows
 *   An array in which store the structured book data.
 *
 * @return
 *   An array containing the structured data of a book.
 */
function _epub_get_book_tree($node, &$rows = array()) {
  $mlid = $node->book['mlid'];
  $rows[$mlid]['book_page'] = $node;
  $result = db_query(db_rewrite_sql("SELECT mlid FROM {menu_links} WHERE plid = %d"), $mlid);
  while ($row = db_fetch_object($result)) {
    $book = db_fetch_object(db_query(db_rewrite_sql("SELECT * FROM {book} WHERE mlid = %d"), $row->mlid));
    _epub_get_book_tree(node_load(array(
      'nid' => $book->nid,
    )), $rows[$mlid]);
  }
  return $rows;
}

/**
 * Append chapters to a book recursively.
 *
 * @param $node
 *   A node object.
 * @param $book
 *   An EPub object.
 *
 * @return
 *   NULL.
 */
function _epub_traverse_book_tree($node, $book) {
  foreach ($node as $key => $child_node) {
    if ($key === 'book_page' && isset($node[$key])) {
      if (count($node[$key]->body) > 250000) {
        $splitter = new EPubChapterSplitter();
        $splitter
          ->setSplitSize(variable_get('epub_custom_chapter_size', 250) * 1000);
        $chapter = $splitter
          ->splitChapter($node[$key]->body);
      }
      else {
        $chapter = $node[$key]->body;
      }
      $book
        ->addChapter($node[$key]->title, $node[$key]->title . '.html', $chapter);
    }
    else {
      _epub_traverse_book_tree($child_node, $book);
    }
  }
}

/**
 * Transfer ePub file to the user
 *
 * @param $data
 *   The ebook binary file, which basically is a zipped xml file.
 * @param $headers
 *   An array containing http headers for the response.
 *
 * @return
 *   NULL.
 */
function _epub_file_transfer($data, $headers) {
  if (ob_get_level()) {
    ob_end_clean();
  }
  foreach ($headers as $header) {

    // To prevent HTTP header injection, we delete new lines that are
    // not followed by a space or a tab.
    // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
    $header = preg_replace('/\\r?\\n(?!\\t| )/', '', $header);
    drupal_set_header($header);
  }
  echo $data;
  exit;
}

Functions

Namesort descending Description
epub_access Implementation of hook_access().
epub_create_file Generate an ePub file from a book.
epub_delete Implementation of hook_delete().
epub_form Implementation of hook_form().
epub_help Implementation of hook_help().
epub_insert Implementation of hook_insert().
epub_list_books_autocomplete Implementation of hook_autocomplete().
epub_load Implementation of hook_load().
epub_menu Implementation of hook_menu().
epub_nodeapi Implementation of hook_nodeapi().
epub_node_info Implementation of hook_node_info().
epub_perm Implementation of hook_perm().
epub_read_access_callback Determine whether the user can download an ePub or not.
epub_read_page_callback Determine whether the user has to create the ePub or can download it.
epub_read_title_callback Determine correct menu title
epub_theme Implementation of hook_theme().
epub_update Implementation of hook_update().
epub_view Implementation of hook_view().
epub_views_api Implementation of hook_views_api().
_epub_add_form_elements Build the common elements of the epub form for the node forms.
_epub_file_transfer Transfer ePub file to the user
_epub_get_book_tree Return a structured book hierarchy.
_epub_links_book Check if ePub links a book
_epub_traverse_book_tree Append chapters to a book recursively.