3 Copyright(c)2008 Internet Archive. Software license AGPL version 3.
5 This file is part of BookReader.
7 BookReader is free software: you can redistribute it and/or modify
8 it under the terms of the GNU Affero General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 BookReader is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Affero General Public License for more details.
17 You should have received a copy of the GNU Affero General Public License
18 along with BookReader. If not, see <http://www.gnu.org/licenses/>.
21 header('Content-Type: application/javascript');
23 $id = $_REQUEST['id'];
24 $itemPath = $_REQUEST['itemPath'];
25 $subPrefix = $_REQUEST['subPrefix'];
26 $server = $_REQUEST['server'];
28 // $$$mang this code has been refactored into BookReaderMeta.inc.php for use e.g. by
29 // BookReaderPreview.php and BookReaderImages.php. The code below should be
30 // taken out and replaced by calls into BookReaderMeta
32 // Check if we're on a dev vhost and point to JSIA in the user's public_html on the datanode
34 // $$$ TODO consolidate this logic
35 if (strpos($_SERVER["REQUEST_URI"], "/~mang") === 0) { // Serving out of home dir
36 $server .= ':80/~mang';
37 } else if (strpos($_SERVER["REQUEST_URI"], "/~rkumar") === 0) { // Serving out of home dir
38 $server .= ':80/~rkumar';
39 } else if (strpos($_SERVER["REQUEST_URI"], "/~testflip") === 0) { // Serving out of home dir
40 $server .= ':80/~testflip';
46 $subItemPath = $itemPath . '/' . $subPrefix;
49 BRFatal("No identifier specified!");
52 if ("" == $itemPath) {
53 BRFatal("No itemPath specified!");
57 BRFatal("No server specified!");
60 if (!preg_match("|^/\d+/items/{$id}$|", $itemPath)) {
64 // XXX check here that subitem is okay
66 $filesDataFile = "$itemPath/${id}_files.xml";
68 if (file_exists($filesDataFile)) {
69 $filesData = simplexml_load_file("$itemPath/${id}_files.xml");
71 BRfatal("File metadata not found!");
74 $imageStackInfo = findImageStack($subPrefix, $filesData);
75 if ($imageStackInfo['imageFormat'] == 'unknown') {
76 BRfatal('Couldn\'t find image stack');
79 $imageFormat = $imageStackInfo['imageFormat'];
80 $archiveFormat = $imageStackInfo['archiveFormat'];
81 $imageStackFile = $itemPath . "/" . $imageStackInfo['imageStackFile'];
83 if ("unknown" == $imageFormat) {
84 BRfatal("Unknown image format");
87 if ("unknown" == $archiveFormat) {
88 BRfatal("Unknown archive format");
92 $scanDataFile = "${subItemPath}_scandata.xml";
93 $scanDataZip = "$itemPath/scandata.zip";
94 if (file_exists($scanDataFile)) {
95 $scanData = simplexml_load_file($scanDataFile);
96 } else if (file_exists($scanDataZip)) {
97 $cmd = 'unzip -p ' . escapeshellarg($scanDataZip) . ' scandata.xml';
98 exec($cmd, $output, $retval);
99 if ($retval != 0) BRFatal("Could not unzip ScanData!");
101 $dump = join("\n", $output);
102 $scanData = simplexml_load_string($dump);
103 } else if (file_exists("$itemPath/scandata.xml")) {
104 // For e.g. Scribe v.0 books!
105 $scanData = simplexml_load_file("$itemPath/scandata.xml");
107 BRFatal("ScanData file not found!");
110 $metaDataFile = "$itemPath/{$id}_meta.xml";
111 if (!file_exists($metaDataFile)) {
112 BRFatal("MetaData file not found!");
116 $metaData = simplexml_load_file($metaDataFile);
118 //$firstLeaf = $scanData->pageData->page[0]['leafNum'];
121 // Error reporting - this helps us fix errors quickly
122 function logError(description,page,line) {
123 if (typeof(archive_analytics) != 'undefined') {
125 'bookreader': 'error',
126 'description': description,
129 'itemid': '<?echo $id;?>',
130 'subPrefix': '<?echo $subPrefix;?>',
131 'server': '<?echo $server;?>',
132 'bookPath': '<?echo $subItemPath;?>'
135 // if no referrer set '-' as referrer
136 if (document.referrer == '') {
137 values['referrer'] = '-';
139 values['referrer'] = document.referrer;
142 if (typeof(br) != 'undefined') {
143 values['itemid'] = br.bookId;
144 values['subPrefix'] = br.subPrefix;
145 values['server'] = br.server;
146 values['bookPath'] = br.bookPath;
149 var qs = archive_analytics.format_bug(values);
151 var error_img = new Image(100,25);
152 error_img.src = archive_analytics.img_src + "?" + qs;
155 return false; // allow browser error handling so user sees there was a problem
157 window.onerror=logError;
159 br = new BookReader();
162 /* Output title leaf if marked */
164 foreach ($scanData->pageData->page as $page) {
165 if (("Title Page" == $page->pageType) || ("Title" == $page->pageType)) {
166 $titleLeaf = "{$page['leafNum']}";
171 if ('' != $titleLeaf) {
172 printf("br.titleLeaf = %d;\n", $titleLeaf);
176 br.getPageWidth = function(index) {
177 return this.pageW[index];
180 br.getPageHeight = function(index) {
181 return this.pageH[index];
184 // Returns true if page image is available rotated
185 br.canRotatePage = function(index) {
186 return 'jp2' == this.imageFormat; // Assume single format for now
189 // reduce defaults to 1 (no reduction)
190 // rotate defaults to 0 (no rotation)
191 br.getPageURI = function(index, reduce, rotate) {
195 if ('undefined' == typeof(reduce)) {
200 if ('undefined' == typeof(rotate)) {
206 var file = this._getPageFile(index);
208 // $$$ add more image stack formats here
209 return 'http://'+this.server+'/BookReader/BookReaderImages.php?zip='+this.zip+'&file='+file+'&scale='+_reduce+'&rotate='+_rotate;
212 br._getPageFile = function(index) {
213 var leafStr = '0000';
214 var imgStr = this.leafMap[index].toString();
215 var re = new RegExp("0{"+imgStr.length+"}$");
217 var insideZipPrefix = this.subPrefix.match('[^/]+$');
218 var file = insideZipPrefix + '_' + this.imageFormat + '/' + insideZipPrefix + '_' + leafStr.replace(re, imgStr) + '.' + this.imageFormat;
223 br.getPageSide = function(index) {
224 //assume the book starts with a cover (right-hand leaf)
225 //we should really get handside from scandata.xml
227 <? // Use special function if we should infer the page sides based off the title page index
228 if (preg_match('/goog$/', $id) && ('' != $titleLeaf)) {
230 // assume page side based on title pagex
231 var titleIndex = br.leafNumToIndex(br.titleLeaf);
232 // assume title page is RHS
233 var delta = titleIndex - index;
234 if (0 == (delta & 0x1)) {
244 // $$$ we should get this from scandata instead of assuming the accessible
245 // leafs are contiguous
246 if ('rl' != this.pageProgression) {
247 // If pageProgression is not set RTL we assume it is LTR
248 if (0 == (index & 0x1)) {
249 // Even-numbered page
257 if (0 == (index & 0x1)) {
265 br.getPageNum = function(index) {
266 var pageNum = this.pageNums[index];
274 // Single images in the Internet Archive scandata.xml metadata are (somewhat incorrectly)
275 // given a "leaf" number. Some of these images from the scanning process should not
276 // be displayed in the BookReader (for example colour calibration cards). Since some
277 // of the scanned images will not be displayed in the BookReader (those marked with
278 // addToAccessFormats false in the scandata.xml) leaf numbers and BookReader page
279 // indexes are generally not the same. This function returns the BookReader page
280 // index given a scanned leaf number.
282 // This function is used, for example, to map between search results (that use the
283 // leaf numbers) and the displayed pages in the BookReader.
284 br.leafNumToIndex = function(leafNum) {
285 for (var index = 0; index < this.leafMap.length; index++) {
286 if (this.leafMap[index] == leafNum) {
294 // This function returns the left and right indices for the user-visible
295 // spread that contains the given index. The return values may be
296 // null if there is no facing page or the index is invalid.
297 br.getSpreadIndices = function(pindex) {
298 // $$$ we could make a separate function for the RTL case and
299 // only bind it if necessary instead of always checking
300 // $$$ we currently assume there are no gaps
302 var spreadIndices = [null, null];
303 if ('rl' == this.pageProgression) {
305 if (this.getPageSide(pindex) == 'R') {
306 spreadIndices[1] = pindex;
307 spreadIndices[0] = pindex + 1;
309 // Given index was LHS
310 spreadIndices[0] = pindex;
311 spreadIndices[1] = pindex - 1;
315 if (this.getPageSide(pindex) == 'L') {
316 spreadIndices[0] = pindex;
317 spreadIndices[1] = pindex + 1;
319 // Given index was RHS
320 spreadIndices[1] = pindex;
321 spreadIndices[0] = pindex - 1;
325 //console.log(" index %d mapped to spread %d,%d", pindex, spreadIndices[0], spreadIndices[1]);
327 return spreadIndices;
330 // Remove the page number assertions for all but the highest index page with
331 // a given assertion. Ensures there is only a single page "{pagenum}"
332 // e.g. the last page asserted as page 5 retains that assertion.
333 br.uniquifyPageNums = function() {
336 for (var i = br.pageNums.length - 1; i--; i >= 0) {
337 var pageNum = br.pageNums[i];
338 if ( !seen[pageNum] ) {
339 seen[pageNum] = true;
341 br.pageNums[i] = null;
347 br.cleanupMetadata = function() {
348 br.uniquifyPageNums();
353 // Returns a URL for an embedded version of the current book
354 br.getEmbedURL = function(viewParams) {
355 // We could generate a URL hash fragment here but for now we just leave at defaults
356 var url = 'http://' + window.location.host + '/stream/'+this.bookId;
357 if (this.subPrefix != this.bookId) { // Only include if needed
358 url += '/' + this.subPrefix;
361 if (typeof(viewParams) != 'undefined') {
362 url += '#' + this.fragmentFromParams(viewParams);
369 // Returns the embed code HTML fragment suitable for copy and paste
370 br.getEmbedCode = function(frameWidth, frameHeight, viewParams) {
371 return "<iframe src='" + this.getEmbedURL(viewParams) + "' width='" + frameWidth + "' height='" + frameHeight + "' frameborder='0' ></iframe>";
374 // getOpenLibraryRecord
375 br.getOpenLibraryRecord = function(callback) {
376 // Try looking up by ocaid first, then by source_record
378 var jsonURL = this.olHost + '/query.json?type=/type/edition&*=&ocaid=' + br.bookId;
381 success: function(data) {
382 if (data && data.length > 0) {
383 callback(br, data[0]);
386 jsonURL = this.olHost + '/query.json?type=/type/edition&*=&source_records=ia:' + br.bookId;
389 success: function(data) {
390 if (data && data.length > 0) {
391 callback(br, data[0]);
402 br.buildInfoDiv = function(jInfoDiv) {
403 // $$$ it might make more sense to have a URL on openlibrary.org that returns this info
405 var escapedTitle = BookReader.util.escapeHTML(this.bookTitle);
406 var domainRe = /(\w+\.(com|org))/;
407 var domainMatch = domainRe.exec(this.bookUrl);
408 var domain = this.bookUrl;
410 domain = domainMatch[1];
413 // $$$ cover looks weird before it loads
414 jInfoDiv.find('.BRfloatCover').append([
415 '<div style="height: 140px; min-width: 80px; padding: 0; margin: 0;"><a href="', this.bookUrl, '"><img src="http://www.archive.org/download/', this.bookId, '/page/cover_t.jpg" alt="' + escapedTitle + '" height="140px" /></a></div>'].join('')
418 jInfoDiv.find('.BRfloatMeta').append([
420 //'<p>Published ', this.bookPublished,
421 //, <a href="Open Library Publisher Page">Publisher name</a>',
423 //'<p>Written in <a href="Open Library Language page">Language</a></p>',
424 '<h3>Other Formats</h3>',
425 '<ul class="links">',
426 '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '.pdf">PDF</a><span>|</span></li>',
427 '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '_djvu.txt">Plain Text</a><span>|</span></li>',
428 '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '_daisy.zip">DAISY</a><span>|</span></li>',
429 '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '.epub">ePub</a><span>|</span></li>',
430 '<li><a href="https://www.amazon.com/gp/digital/fiona/web-to-kindle?clientid=IA&itemid=', this.bookId, '&docid=', this.subPrefix, '">Send to Kindle</a></li>',
432 '<p class="moreInfo"><span></span>More information on <a href="'+ this.bookUrl + '">' + domain + '</a> </p>'].join('\n'));
434 jInfoDiv.find('.BRfloatFoot').append([
436 '<a href="http://openlibrary.org/contact" class="problem">Report a problem</a>',
439 if (domain == 'archive.org') {
440 jInfoDiv.find('.BRfloatMeta p.moreInfo span').css(
441 {'background': 'url(http://www.archive.org/favicon.ico) no-repeat', 'width': 22, 'height': 18 }
445 jInfoDiv.find('.BRfloatTitle a').attr({'href': this.bookUrl, 'alt': this.bookTitle}).text(this.bookTitle);
446 var bookPath = (window.location + '').replace('#','%23');
447 jInfoDiv.find('a.problem').attr('href','http://openlibrary.org/contact?path=' + bookPath);
454 foreach ($scanData->pageData->page as $page) {
455 if (shouldAddPage($page)) {
456 if(0 != $i) echo ","; //stupid IE
457 echo "{$page->cropBox->w}";
468 foreach ($scanData->pageData->page as $page) {
469 if (shouldAddPage($page)) {
470 if(0 != $i) echo ","; //stupid IE
471 echo "{$page->cropBox->h}";
472 $totalHeight += intval($page->cropBox->h/4) + 10;
481 foreach ($scanData->pageData->page as $page) {
482 if (shouldAddPage($page)) {
483 if(0 != $i) echo ","; //stupid IE
484 echo "{$page['leafNum']}";
494 foreach ($scanData->pageData->page as $page) {
495 if (shouldAddPage($page)) {
496 if(0 != $i) echo ","; //stupid IE
497 if (array_key_exists('pageNumber', $page) && ('' != $page->pageNumber)) {
498 echo "'{$page->pageNumber}'";
509 br.numLeafs = br.pageW.length;
511 br.bookId = '<?echo $id;?>';
512 br.zip = '<?echo $imageStackFile;?>';
513 br.subPrefix = '<?echo $subPrefix;?>';
514 br.server = '<?echo $server;?>';
515 br.bookTitle= '<?echo preg_replace("/\'/", "\\'", $metaData->title);?>';
516 br.bookPath = '<?echo $subItemPath;?>';
517 br.bookUrl = '<?echo "http://www.archive.org/details/$id";?>';
518 br.imageFormat = '<?echo $imageFormat;?>';
519 br.archiveFormat = '<?echo $archiveFormat;?>';
523 # Load some values from meta.xml
524 if ('' != $metaData->{'page-progression'}) {
525 echo "br.pageProgression = '" . $metaData->{"page-progression"} . "';\n";
527 // Assume page progression is Left To Right
528 echo "br.pageProgression = 'lr';\n";
532 foreach ($metaData->xpath('//collection') as $collection) {
533 if('browserlending' == $collection) {
538 echo "br.olHost = 'http://openlibrary.org'\n";
539 #echo "br.olHost = 'http://ol-mang:8080'\n";
542 echo "br.olAuth = true;\n";
544 echo "br.olAuth = false;\n";
548 if ('bandersnatchhsye00scarrich' == $id) {
549 echo "br.mode = 2;\n";
550 echo "br.auto = true;\n";
555 // Check for config object
556 // $$$ change this to use the newer params object
557 if (typeof(brConfig) != 'undefined') {
558 if (typeof(brConfig["ui"]) != 'undefined') {
559 br.ui = brConfig["ui"];
562 if (brConfig['mode'] == 1) {
564 if (typeof(brConfig['reduce'] != 'undefined')) {
565 br.reduce = brConfig['reduce'];
567 } else if (brConfig['mode'] == 2) {
574 this.authUrl = br.olHost + '/ia_auth/' + br.bookId;
575 this.olConnect = false;
576 this.loanUUID = false;
577 this.loanToken = false;
579 var cookieRe = /;\s*/;
580 var cookies = document.cookie.split(cookieRe);
581 var length = cookies.length;
583 for (i=0; i<length; i++) {
584 if (0 == cookies[i].indexOf('br-loan-' + br.bookId)) {
585 this.loanUUID = cookies[i].split('=')[1];
587 if (0 == cookies[i].indexOf('loan-' + br.bookId)) {
588 this.loanToken = cookies[i].split('=')[1];
595 OLAuth.prototype.init = function() {
596 var htmlStr = 'Checking loan status with Open Library';
598 this.showPopup("#ddd", "#000", htmlStr, 'Please wait as we check the status of this book...');
599 var authUrl = this.authUrl+'?rand='+Math.random();
600 if (false !== this.loanUUID) {
601 authUrl += '&loan='+this.loanUUID
603 if (false !== this.loanToken) {
604 authUrl += '&token='+this.loanToken
606 $.ajax({url:authUrl, dataType:'jsonp', jsonpCallback:'olAuth.initCallback'});
609 OLAuth.prototype.showPopup = function(bgColor, textColor, msg, resolution) {
610 this.popup = document.createElement("div");
612 position: 'absolute',
614 left: ($('#BookReader').attr('clientWidth')-400)/2 + 'px',
617 border: "3px double #999999",
620 backgroundColor: bgColor,
622 }).appendTo('#BookReader');
624 this.setPopupMsg(msg, resolution);
628 OLAuth.prototype.setPopupMsg = function(msg, resolution) {
629 this.popup.innerHTML = ['<p><strong>', msg, '</strong></p><p>', resolution, '</p>'].join('\n');
632 OLAuth.prototype.initCallback = function(obj) {
633 if (false == obj.success) {
635 backgroundColor: "#fff",
639 this.setPopupMsg(obj.msg, obj.resolution);
643 //user is authenticated
644 this.setCookie(obj.token);
645 this.olConnect = true;
650 OLAuth.prototype.callback = function(obj) {
651 if (false == obj.success) {
652 this.showPopup("#fff", "#000", obj.msg, obj.resolution);
653 clearInterval(this.poller);
654 this.ttsPoller = null;
656 this.olConnect = true;
657 this.setCookie(obj.token);
661 OLAuth.prototype.setCookie = function(value) {
662 var date = new Date();
663 date.setTime(date.getTime()+(10*60*1000)); //10 min expiry
664 var expiry = date.toGMTString();
665 var cookie = 'loan-'+br.bookId+'='+value;
666 cookie += '; expires='+expiry;
667 cookie += '; path=/; domain=.archive.org;';
668 document.cookie = cookie;
670 //refresh the br-loan uuid cookie with current expiry, if needed
671 if (false !== this.loanUUID) {
672 cookie = 'br-loan-'+br.bookId+'='+this.loanUUID;
673 cookie += '; expires='+expiry;
674 cookie += '; path=/; domain=.archive.org;';
675 document.cookie = cookie;
679 OLAuth.prototype.deleteCookies = function() {
680 var date = new Date();
681 date.setTime(date.getTime()-(24*60*60*1000)); //one day ago
682 var expiry = date.toGMTString();
683 var cookie = 'loan-'+br.bookId+'=""';
684 cookie += '; expires='+expiry;
685 cookie += '; path=/; domain=.archive.org;';
686 document.cookie = cookie;
688 cookie = 'br-loan-'+br.bookId+'=""';
689 cookie += '; expires='+expiry;
690 cookie += '; path=/; domain=.archive.org;';
691 document.cookie = cookie;
694 OLAuth.prototype.startPolling = function () {
696 this.poller=setInterval(function(){
697 if (!self.olConnect) {
698 self.showPopup("#f00", "#fff", 'Connection error', 'The BookReader cannot reach Open Library. This might mean that you are offline or that Open Library is down. Please check your Internet connection or try again later.');
699 clearInterval(self.poller);
700 self.ttsPoller = null;
702 self.olConnect = false;
703 //be sure to add random param to authUrl to avoid stale cache
704 var authUrl = self.authUrl+'?rand='+Math.random();
705 if (false !== self.loanUUID) {
706 authUrl += '&loan='+self.loanUUID
708 if (false !== self.loanToken) {
709 authUrl += '&token='+self.loanToken
712 $.ajax({url:authUrl, dataType:'jsonp', jsonpCallback:'olAuth.callback'});
714 },300000); //five minute interval
717 br.cleanupMetadata();
719 var olAuth = new OLAuth();
727 function BRFatal($string) {
731 if (typeof(archive_analytics) != 'undefined') {
733 'bookreader': 'fatal',
734 'description': "<? echo $string; ?>",
735 'itemid': "<? echo $_REQUEST['id']; ?>",
736 'server': "<? echo $_REQUEST['server']; ?>",
737 'request_uri': "<? echo $_SERVER["REQUEST_URI"]; ?>"
740 if (document.referrer == '') {
741 values['referrer'] = '-';
743 values['referrer'] = document.referrer;
746 var qs = archive_analytics.format_bug(values);
748 var error_img = new Image(100,25);
749 error_img.src = archive_analytics.img_src + "?" + qs;
752 alert("<? echo $string;?>");
759 // Returns true if a page should be added based on it's information in
761 function shouldAddPage($page) {
762 // Return false only if the page is marked addToAccessFormats false.
763 // If there is no assertion we assume it should be added.
764 if (isset($page->addToAccessFormats)) {
765 if ("false" == strtolower(trim($page->addToAccessFormats))) {
773 // Returns { 'imageFormat' => , 'archiveFormat' => '} given a sub-item prefix and loaded xml data
774 function findImageStack($subPrefix, $filesData) {
776 // $$$ The order of the image formats determines which will be returned first
777 $imageFormats = array('JP2' => 'jp2', 'TIFF' => 'tif', 'JPEG' => 'jpg');
778 $archiveFormats = array('ZIP' => 'zip', 'Tar' => 'tar');
779 $imageGroup = implode('|', array_keys($imageFormats));
780 $archiveGroup = implode('|', array_keys($archiveFormats));
781 // $$$ Currently only return processed images
782 $imageStackRegex = "/Single Page (Processed) (${imageGroup}) (${archiveGroup})/";
784 foreach ($filesData->file as $file) {
785 if (strpos($file['name'], $subPrefix) === 0) { // subprefix matches beginning
786 if (preg_match($imageStackRegex, $file->format, $matches)) {
788 // Make sure we have a regular image stack
789 $imageFormat = $imageFormats[$matches[2]];
790 if (strpos($file['name'], $subPrefix . '_' . $imageFormat) === 0) {
791 return array('imageFormat' => $imageFormat,
792 'archiveFormat' => $archiveFormats[$matches[3]],
793 'imageStackFile' => $file['name']);
799 return array('imageFormat' => 'unknown', 'archiveFormat' => 'unknown', 'imageStackFile' => 'unknown');