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";
533 foreach ($metaData->xpath('//collection') as $collection) {
534 if('browserlending' == $collection) {
540 echo "br.olHost = 'http://openlibrary.org'\n";
541 #echo "br.olHost = 'http://ol-mang:8080'\n";
544 echo "br.olAuth = true;\n";
546 echo "br.olAuth = false;\n";
550 echo "br.protected = true;\n";
554 if ('bandersnatchhsye00scarrich' == $id) {
555 echo "br.mode = 2;\n";
556 echo "br.auto = true;\n";
561 // Check for config object
562 // $$$ change this to use the newer params object
563 if (typeof(brConfig) != 'undefined') {
564 if (typeof(brConfig["ui"]) != 'undefined') {
565 br.ui = brConfig["ui"];
568 if (brConfig['mode'] == 1) {
570 if (typeof(brConfig['reduce'] != 'undefined')) {
571 br.reduce = brConfig['reduce'];
573 } else if (brConfig['mode'] == 2) {
580 this.authUrl = br.olHost + '/ia_auth/' + br.bookId;
581 this.olConnect = false;
582 this.loanUUID = false;
583 this.permsToken = false;
585 var cookieRe = /;\s*/;
586 var cookies = document.cookie.split(cookieRe);
587 var length = cookies.length;
589 for (i=0; i<length; i++) {
590 if (0 == cookies[i].indexOf('br-loan-' + br.bookId)) {
591 this.loanUUID = cookies[i].split('=')[1];
593 if (0 == cookies[i].indexOf('loan-' + br.bookId)) {
594 this.permsToken = cookies[i].split('=')[1];
601 OLAuth.prototype.init = function() {
602 var htmlStr = 'Checking loan status with Open Library';
604 this.showPopup("#F0EEE2", "#000", htmlStr, 'Please wait as we check the status of this book...');
605 var authUrl = this.authUrl+'?rand='+Math.random();
606 if (false !== this.loanUUID) {
607 authUrl += '&loan='+this.loanUUID
609 if (false !== this.permsToken) {
610 authUrl += '&token='+this.permsToken
612 $.ajax({url:authUrl, dataType:'jsonp', jsonpCallback:'olAuth.initCallback'});
615 OLAuth.prototype.showPopup = function(bgColor, textColor, msg, resolution) {
616 this.popup = document.createElement("div");
618 position: 'absolute',
620 left: ($('#BookReader').attr('clientWidth')-400)/2 + 'px',
623 border: "3px double #999999",
626 backgroundColor: bgColor,
628 }).appendTo('#BookReader');
630 this.setPopupMsg(msg, resolution);
634 OLAuth.prototype.setPopupMsg = function(msg, resolution) {
635 this.popup.innerHTML = ['<p><strong>', msg, '</strong></p><p>', resolution, '</p>'].join('\n');
638 OLAuth.prototype.isAdmin = function() {
641 $.ajax({url:'/bookreader/BookReaderAdmin.php?id='+br.bookId,
642 success:function(data){isAdmin=data.isAdmin;},
648 OLAuth.prototype.showError = function(msg, resolution) {
650 backgroundColor: "#fff",
654 this.setPopupMsg(msg, resolution);
657 OLAuth.prototype.initCallback = function(obj) {
658 if (false == obj.success) {
659 if (this.isAdmin()) {
660 ret = confirm("We couldn't authenticate your loan with Open Library, but since you are an administrator or uploader of this book, you can access this book for QA purposes. Would you like to QA this book?");
662 this.showError(obj.msg, obj.resolution)
667 this.showError(obj.msg, obj.resolution)
670 //user is authenticated
671 this.setCookie(obj.token);
672 this.olConnect = true;
678 OLAuth.prototype.callback = function(obj) {
679 if (false == obj.success) {
680 this.showPopup("#F0EEE2", "#000", obj.msg, obj.resolution);
681 clearInterval(this.poller);
682 this.ttsPoller = null;
684 this.olConnect = true;
685 this.setCookie(obj.token);
689 OLAuth.prototype.setCookie = function(value) {
690 var date = new Date();
691 date.setTime(date.getTime()+(10*60*1000)); //10 min expiry
692 var expiry = date.toGMTString();
693 var cookie = 'loan-'+br.bookId+'='+value;
694 cookie += '; expires='+expiry;
695 cookie += '; path=/; domain=.archive.org;';
696 document.cookie = cookie;
697 this.permsToken = value;
699 //refresh the br-loan uuid cookie with current expiry, if needed
700 if (false !== this.loanUUID) {
701 cookie = 'br-loan-'+br.bookId+'='+this.loanUUID;
702 cookie += '; expires='+expiry;
703 cookie += '; path=/; domain=.archive.org;';
704 document.cookie = cookie;
708 OLAuth.prototype.deleteCookies = function() {
709 var date = new Date();
710 date.setTime(date.getTime()-(24*60*60*1000)); //one day ago
711 var expiry = date.toGMTString();
712 var cookie = 'loan-'+br.bookId+'=""';
713 cookie += '; expires='+expiry;
714 cookie += '; path=/; domain=.archive.org;';
715 document.cookie = cookie;
717 cookie = 'br-loan-'+br.bookId+'=""';
718 cookie += '; expires='+expiry;
719 cookie += '; path=/; domain=.archive.org;';
720 document.cookie = cookie;
723 OLAuth.prototype.startPolling = function () {
725 this.poller=setInterval(function(){
726 if (!self.olConnect) {
727 self.showPopup("#F0EEE2", "#000", '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 and refresh this page or try again later.');
728 clearInterval(self.poller);
729 self.ttsPoller = null;
731 self.olConnect = false;
732 //be sure to add random param to authUrl to avoid stale cache
733 var authUrl = self.authUrl+'?rand='+Math.random();
734 if (false !== self.loanUUID) {
735 authUrl += '&loan='+self.loanUUID
737 if (false !== self.permsToken) {
738 authUrl += '&token='+self.permsToken
741 $.ajax({url:authUrl, dataType:'jsonp', jsonpCallback:'olAuth.callback'});
743 },300000); //five minute interval
746 br.cleanupMetadata();
748 var olAuth = new OLAuth();
756 function BRFatal($string) {
760 if (typeof(archive_analytics) != 'undefined') {
762 'bookreader': 'fatal',
763 'description': "<? echo $string; ?>",
764 'itemid': "<? echo $_REQUEST['id']; ?>",
765 'server': "<? echo $_REQUEST['server']; ?>",
766 'request_uri': "<? echo $_SERVER["REQUEST_URI"]; ?>"
769 if (document.referrer == '') {
770 values['referrer'] = '-';
772 values['referrer'] = document.referrer;
775 var qs = archive_analytics.format_bug(values);
777 var error_img = new Image(100,25);
778 error_img.src = archive_analytics.img_src + "?" + qs;
781 alert("<? echo $string;?>");
788 // Returns true if a page should be added based on it's information in
790 function shouldAddPage($page) {
791 // Return false only if the page is marked addToAccessFormats false.
792 // If there is no assertion we assume it should be added.
793 if (isset($page->addToAccessFormats)) {
794 if ("false" == strtolower(trim($page->addToAccessFormats))) {
802 // Returns { 'imageFormat' => , 'archiveFormat' => '} given a sub-item prefix and loaded xml data
803 function findImageStack($subPrefix, $filesData) {
805 // $$$ The order of the image formats determines which will be returned first
806 $imageFormats = array('JP2' => 'jp2', 'TIFF' => 'tif', 'JPEG' => 'jpg');
807 $archiveFormats = array('ZIP' => 'zip', 'Tar' => 'tar');
808 $imageGroup = implode('|', array_keys($imageFormats));
809 $archiveGroup = implode('|', array_keys($archiveFormats));
810 // $$$ Currently only return processed images
811 $imageStackRegex = "/Single Page (Processed) (${imageGroup}) (${archiveGroup})/";
813 foreach ($filesData->file as $file) {
814 if (strpos($file['name'], $subPrefix) === 0) { // subprefix matches beginning
815 if (preg_match($imageStackRegex, $file->format, $matches)) {
817 // Make sure we have a regular image stack
818 $imageFormat = $imageFormats[$matches[2]];
819 if (strpos($file['name'], $subPrefix . '_' . $imageFormat) === 0) {
820 return array('imageFormat' => $imageFormat,
821 'archiveFormat' => $archiveFormats[$matches[3]],
822 'imageStackFile' => $file['name']);
828 return array('imageFormat' => 'unknown', 'archiveFormat' => 'unknown', 'imageStackFile' => 'unknown');