Merge branch 'newui' of git@github.com:openlibrary/bookreader into newui
[bookreader.git] / BookReaderIA / datanode / BookReaderJSIA.php
1 <?
2 /*
3 Copyright(c)2008 Internet Archive. Software license AGPL version 3.
4
5 This file is part of BookReader.
6
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.
11
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.
16
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/>.
19 */
20
21 header('Content-Type: application/javascript');
22
23 $id = $_REQUEST['id'];
24 $itemPath = $_REQUEST['itemPath'];
25 $subPrefix = $_REQUEST['subPrefix'];
26 $server = $_REQUEST['server'];
27
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
31
32 // Check if we're on a dev vhost and point to JSIA in the user's public_html on the datanode
33
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';
41 }
42
43 if (! $subPrefix) {
44     $subPrefix = $id;
45 }
46 $subItemPath = $itemPath . '/' . $subPrefix;
47
48 if ("" == $id) {
49     BRFatal("No identifier specified!");
50 }
51
52 if ("" == $itemPath) {
53     BRFatal("No itemPath specified!");
54 }
55
56 if ("" == $server) {
57     BRFatal("No server specified!");
58 }
59
60 if (!preg_match("|^/\d+/items/{$id}$|", $itemPath)) {
61     BRFatal("Bad id!");
62 }
63
64 // XXX check here that subitem is okay
65
66 $filesDataFile = "$itemPath/${id}_files.xml";
67
68 if (file_exists($filesDataFile)) {
69     $filesData = simplexml_load_file("$itemPath/${id}_files.xml");
70 } else {
71     BRfatal("File metadata not found!");
72 }
73
74 $imageStackInfo = findImageStack($subPrefix, $filesData);
75 if ($imageStackInfo['imageFormat'] == 'unknown') {
76     BRfatal('Couldn\'t find image stack');
77 }
78
79 $imageFormat = $imageStackInfo['imageFormat'];
80 $archiveFormat = $imageStackInfo['archiveFormat'];
81 $imageStackFile = $itemPath . "/" . $imageStackInfo['imageStackFile'];
82
83 if ("unknown" == $imageFormat) {
84   BRfatal("Unknown image format");
85 }
86
87 if ("unknown" == $archiveFormat) {
88   BRfatal("Unknown archive format");
89 }
90
91
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!");
100     
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");
106 } else {
107     BRFatal("ScanData file not found!");
108 }
109
110 $metaDataFile = "$itemPath/{$id}_meta.xml";
111 if (!file_exists($metaDataFile)) {
112     BRFatal("MetaData file not found!");
113 }
114
115
116 $metaData = simplexml_load_file($metaDataFile);
117
118 //$firstLeaf = $scanData->pageData->page[0]['leafNum'];
119 ?>
120
121 br = new BookReader();
122
123 <?
124 /* Output title leaf if marked */
125 $titleLeaf = '';
126 foreach ($scanData->pageData->page as $page) {
127     if (("Title Page" == $page->pageType) || ("Title" == $page->pageType)) {
128         $titleLeaf = "{$page['leafNum']}";
129         break;
130     }
131 }
132     
133 if ('' != $titleLeaf) {
134     printf("br.titleLeaf = %d;\n", $titleLeaf);
135 }
136 ?>
137
138 br.getPageWidth = function(index) {
139     return this.pageW[index];
140 }
141
142 br.getPageHeight = function(index) {
143     return this.pageH[index];
144 }
145
146 // Returns true if page image is available rotated
147 br.canRotatePage = function(index) {
148     return 'jp2' == this.imageFormat; // Assume single format for now
149 }
150
151 // reduce defaults to 1 (no reduction)
152 // rotate defaults to 0 (no rotation)
153 br.getPageURI = function(index, reduce, rotate) {
154     var _reduce;
155     var _rotate;
156
157     if ('undefined' == typeof(reduce)) {
158         _reduce = 1;
159     } else {
160         _reduce = reduce;
161     }
162     if ('undefined' == typeof(rotate)) {
163         _rotate = 0;
164     } else {
165         _rotate = rotate;
166     }
167     
168     var file = this._getPageFile(index);
169         
170     // $$$ add more image stack formats here
171     return 'http://'+this.server+'/BookReader/BookReaderImages.php?zip='+this.zip+'&file='+file+'&scale='+_reduce+'&rotate='+_rotate;
172 }
173
174 br._getPageFile = function(index) {
175     var leafStr = '0000';
176     var imgStr = this.leafMap[index].toString();
177     var re = new RegExp("0{"+imgStr.length+"}$");
178     
179     var insideZipPrefix = this.subPrefix.match('[^/]+$');
180     var file = insideZipPrefix + '_' + this.imageFormat + '/' + insideZipPrefix + '_' + leafStr.replace(re, imgStr) + '.' + this.imageFormat;
181     
182     return file;
183 }
184
185 br.getPageSide = function(index) {
186     //assume the book starts with a cover (right-hand leaf)
187     //we should really get handside from scandata.xml
188     
189     <? // Use special function if we should infer the page sides based off the title page index
190     if (preg_match('/goog$/', $id) && ('' != $titleLeaf)) {
191     ?>
192     // assume page side based on title pagex
193     var titleIndex = br.leafNumToIndex(br.titleLeaf);
194     // assume title page is RHS
195     var delta = titleIndex - index;
196     if (0 == (delta & 0x1)) {
197         // even delta
198         return 'R';
199     } else {
200         return 'L';
201     }
202     <?
203     }
204     ?>
205     
206     // $$$ we should get this from scandata instead of assuming the accessible
207     //     leafs are contiguous
208     if ('rl' != this.pageProgression) {
209         // If pageProgression is not set RTL we assume it is LTR
210         if (0 == (index & 0x1)) {
211             // Even-numbered page
212             return 'R';
213         } else {
214             // Odd-numbered page
215             return 'L';
216         }
217     } else {
218         // RTL
219         if (0 == (index & 0x1)) {
220             return 'L';
221         } else {
222             return 'R';
223         }
224     }
225 }
226
227 br.getPageNum = function(index) {
228     var pageNum = this.pageNums[index];
229     if (pageNum) {
230         return pageNum;
231     } else {
232         return 'n' + index;
233     }
234 }
235
236 // Single images in the Internet Archive scandata.xml metadata are (somewhat incorrectly)
237 // given a "leaf" number.  Some of these images from the scanning process should not
238 // be displayed in the BookReader (for example colour calibration cards).  Since some
239 // of the scanned images will not be displayed in the BookReader (those marked with
240 // addToAccessFormats false in the scandata.xml) leaf numbers and BookReader page
241 // indexes are generally not the same.  This function returns the BookReader page
242 // index given a scanned leaf number.
243 //
244 // This function is used, for example, to map between search results (that use the
245 // leaf numbers) and the displayed pages in the BookReader.
246 br.leafNumToIndex = function(leafNum) {
247     for (var index = 0; index < this.leafMap.length; index++) {
248         if (this.leafMap[index] == leafNum) {
249             return index;
250         }
251     }
252     
253     return null;
254 }
255
256 // This function returns the left and right indices for the user-visible
257 // spread that contains the given index.  The return values may be
258 // null if there is no facing page or the index is invalid.
259 br.getSpreadIndices = function(pindex) {
260     // $$$ we could make a separate function for the RTL case and
261     //      only bind it if necessary instead of always checking
262     // $$$ we currently assume there are no gaps
263     
264     var spreadIndices = [null, null]; 
265     if ('rl' == this.pageProgression) {
266         // Right to Left
267         if (this.getPageSide(pindex) == 'R') {
268             spreadIndices[1] = pindex;
269             spreadIndices[0] = pindex + 1;
270         } else {
271             // Given index was LHS
272             spreadIndices[0] = pindex;
273             spreadIndices[1] = pindex - 1;
274         }
275     } else {
276         // Left to right
277         if (this.getPageSide(pindex) == 'L') {
278             spreadIndices[0] = pindex;
279             spreadIndices[1] = pindex + 1;
280         } else {
281             // Given index was RHS
282             spreadIndices[1] = pindex;
283             spreadIndices[0] = pindex - 1;
284         }
285     }
286     
287     //console.log("   index %d mapped to spread %d,%d", pindex, spreadIndices[0], spreadIndices[1]);
288     
289     return spreadIndices;
290 }
291
292 // Remove the page number assertions for all but the highest index page with
293 // a given assertion.  Ensures there is only a single page "{pagenum}"
294 // e.g. the last page asserted as page 5 retains that assertion.
295 br.uniquifyPageNums = function() {
296     var seen = {};
297     
298     for (var i = br.pageNums.length - 1; i--; i >= 0) {
299         var pageNum = br.pageNums[i];
300         if ( !seen[pageNum] ) {
301             seen[pageNum] = true;
302         } else {
303             br.pageNums[i] = null;
304         }
305     }
306
307 }
308
309 br.cleanupMetadata = function() {
310     br.uniquifyPageNums();
311 }
312
313 // getEmbedURL
314 //________
315 // Returns a URL for an embedded version of the current book
316 br.getEmbedURL = function(viewParams) {
317     // We could generate a URL hash fragment here but for now we just leave at defaults
318     var url = 'http://' + window.location.host + '/stream/'+this.bookId;
319     if (this.subPrefix != this.bookId) { // Only include if needed
320         url += '/' + this.subPrefix;
321     }
322     url += '?ui=embed';
323     if (viewParams) {
324         url += '#' + this.fragmentFromParams(viewParams);
325     }
326     return url;
327 }
328
329 // getEmbedCode
330 //________
331 // Returns the embed code HTML fragment suitable for copy and paste
332 br.getEmbedCode = function() {
333     return "<iframe src='" + this.getEmbedURL() + "' width='480px' height='430px'></iframe>";
334 }
335
336 // getOpenLibraryRecord
337 br.getOpenLibraryRecord = function(callback) {
338     // Try looking up by ocaid first, then by source_record
339     
340     var jsonURL = 'http://openlibrary.org/query.json?type=/type/edition&*=&ocaid=' + br.bookId;
341     $.ajax({
342         url: jsonURL,
343         success: function(data) {
344             if (data && data.length > 0) {
345                 callback(br, data[0]);
346             } else {
347                 // try sourceid
348                 jsonURL = 'http://openlibrary.org/query.json?type=/type/edition&*=&source_records=ia:' + br.bookId;
349                 $.ajax({
350                     url: jsonURL,
351                     success: function(data) {
352                         if (data && data.length > 0) {
353                             callback(br, data[0]);
354                         }
355                     },
356                     dataType: 'jsonp'
357                 });
358             }
359         },
360         dataType: 'jsonp'
361     });
362 }
363
364 // getInfoDiv
365 br.getInfoDiv = function() {
366     // $$$ it might make more sense to have a URL on openlibrary.org that returns this info
367
368     var escapedTitle = BookReader.util.escapeHTML(this.bookTitle);
369     var domainRe = /(\w+\.(com|org))/;
370     var domain = domainRe.exec(this.bookUrl)[1];
371     // XXX use different icon for archive.org
372     var html = [
373                 '<div class="BRfloatCover">',
374                     '<a href="', this.bookUrl, '"><img src="http://www.archive.org/download/', this.bookId, '/page/cover_t.jpg" alt="', escapedTitle, '" height="140"/></a>',
375                 '</div>',
376                 '<div class="BRfloatMeta">',
377                     '<div class="BRfloatTitle">',
378                         '<h2><a href="', this.bookUrl, '" class="title">', escapedTitle, '</a></h2>',
379                         // $$$ lookup on OL
380                         // 'by',
381                         // '<a href="Open Library Author Page">Book Author</a>',
382                     '</div>',
383                     '<p>Published ', this.bookPublished,
384                     //, <a href="Open Library Publisher Page">Publisher name</a>',
385                     '</p>',
386                     //'<p>Written in <a href="Open Library Language page">Language</a></p>',
387                     '<h3>Other Formats</h3>',
388                     '<ul class="links">',
389                         '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '.pdf">PDF</a><span>|</span></li>',
390                         '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '_djvu.txt">Plain Text</a><span>|</span></li>',
391                         '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '_daisy.zip">DAISY</a><span>|</span></li>',
392                         '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '.epub">ePub</a><span>|</span></li>',
393                         '<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><span>|</span></li>',
394                         '<li><a href="', this.bookUrl, '">More...</a></li>',
395                     '</ul>',
396                     '<p class="moreInfo"><span></span>More information on <a href="'+ this.bookUrl + '">' + domain + '</a>.</p>',
397                 '</div>',
398             '</div>',
399             '<div class="BRfloatFoot">',
400                 // XXX add link to bug tracker
401                 '<a href="http://openlibrary.org/contact" class="problem">Report a problem</a>',
402                 '<span>|</span>',
403                 '<a href="http://openlibrary.org/dev/docs/bookreader">About the Bookreader</a>',
404             '</div>'
405     ];
406     
407     return html.join('\n');
408 }
409
410 br.pageW =  [
411             <?
412             $i=0;
413             foreach ($scanData->pageData->page as $page) {
414                 if (shouldAddPage($page)) {
415                     if(0 != $i) echo ",";   //stupid IE
416                     echo "{$page->cropBox->w}";
417                     $i++;
418                 }
419             }
420             ?>
421             ];
422
423 br.pageH =  [
424             <?
425             $totalHeight = 0;
426             $i=0;            
427             foreach ($scanData->pageData->page as $page) {
428                 if (shouldAddPage($page)) {
429                     if(0 != $i) echo ",";   //stupid IE                
430                     echo "{$page->cropBox->h}";
431                     $totalHeight += intval($page->cropBox->h/4) + 10;
432                     $i++;
433                 }
434             }
435             ?>
436             ];
437 br.leafMap = [
438             <?
439             $i=0;
440             foreach ($scanData->pageData->page as $page) {
441                 if (shouldAddPage($page)) {
442                     if(0 != $i) echo ",";   //stupid IE
443                     echo "{$page['leafNum']}";
444                     $i++;
445                 }
446             }
447             ?>    
448             ];
449
450 br.pageNums = [
451             <?
452             $i=0;
453             foreach ($scanData->pageData->page as $page) {
454                 if (shouldAddPage($page)) {
455                     if(0 != $i) echo ",";   //stupid IE                
456                     if (array_key_exists('pageNumber', $page) && ('' != $page->pageNumber)) {
457                         echo "'{$page->pageNumber}'";
458                     } else {
459                         echo "null";
460                     }
461                     $i++;
462                 }
463             }
464             ?>    
465             ];
466             
467       
468 br.numLeafs = br.pageW.length;
469
470 br.bookId   = '<?echo $id;?>';
471 br.zip      = '<?echo $imageStackFile;?>';
472 br.subPrefix = '<?echo $subPrefix;?>';
473 br.server   = '<?echo $server;?>';
474 br.bookTitle= '<?echo preg_replace("/\'/", "\\'", $metaData->title);?>';
475 br.bookPath = '<?echo $subItemPath;?>';
476 br.bookUrl  = '<?echo "http://www.archive.org/details/$id";?>';
477 br.imageFormat = '<?echo $imageFormat;?>';
478 br.archiveFormat = '<?echo $archiveFormat;?>';
479
480 <?
481
482 # Load some values from meta.xml
483 if ('' != $metaData->{'page-progression'}) {
484   echo "br.pageProgression = '" . $metaData->{"page-progression"} . "';\n";
485 } else {
486   // Assume page progression is Left To Right
487   echo "br.pageProgression = 'lr';\n";
488 }
489
490 $useOLAuth = false;
491 foreach ($metaData->xpath('//collection') as $collection) {
492     if('browserlending' == $collection) {
493         $useOLAuth = true;
494     }
495 }
496
497 if ($useOLAuth) {
498     echo "br.olAuth = true;\n";
499 } else {
500     echo "br.olAuth = false;\n";
501 }
502
503 # Special cases
504 if ('bandersnatchhsye00scarrich' == $id) {
505     echo "br.mode     = 2;\n";
506     echo "br.auto     = true;\n";
507 }
508
509 ?>
510
511 // Check for config object
512 // $$$ change this to use the newer params object
513 if (typeof(brConfig) != 'undefined') {
514     if (typeof(brConfig["ui"]) != 'undefined') {
515         br.ui = brConfig["ui"];
516     }
517
518     if (brConfig['mode'] == 1) {
519         br.mode = 1;
520         if (typeof(brConfig['reduce'] != 'undefined')) {
521             br.reduce = brConfig['reduce'];
522         }
523     } else if (brConfig['mode'] == 2) {
524         br.mode = 2;
525       
526 <?
527         //$$$mang hack to override request for 2up for books with attribution page
528         //   as first page until we can display that page in 2up
529         $needle = 'goog';
530         if (strrpos($id, $needle) === strlen($id)-strlen($needle)) {
531             print "// override for books with attribution page\n";
532             print "br.mode = 1;\n";
533         }
534 ?>
535     }
536 } // brConfig
537
538
539 function OLAuth() {
540     this.authUrl = 'http://openlibrary.org/ia_auth/' + br.bookId;
541     this.olConnect = false;
542     return this;
543 }
544
545 OLAuth.prototype.init = function() {
546     var htmlStr =  '<p style="text-align:center;"><b>Authenticating in-browser loan with openlibrary.org!</b></p>';
547     htmlStr    +=  '<p>Please wait...</p>';
548
549     this.showPopup("#ddd", "#000", htmlStr);
550     $.ajax({url:this.authUrl, dataType:'jsonp', jsonpCallback:'olAuth.initCallback'});
551 }
552
553 OLAuth.prototype.showPopup = function(bgColor, textColor, msg) {
554     this.popup = document.createElement("div");
555     $(this.popup).css({
556         position: 'absolute',
557         top:      '20px',
558         left:     ($('#BookReader').attr('clientWidth')-400)/2 + 'px',
559         width:    '400px',
560         padding:  "20px",
561         border:   "3px double #999999",
562         zIndex:   3,
563         backgroundColor: bgColor,
564         color: textColor
565     }).appendTo('#BookReader');
566
567     this.popup.innerHTML = msg;
568
569 }
570
571 OLAuth.prototype.initCallback = function(obj) {
572     if (false == obj.success) {
573         $(this.popup).css({
574             backgroundColor: "#f00",
575             color: "#fff"
576         });
577
578         this.popup.innerHTML = obj.msg;
579         return;
580     }
581     
582     //user is authenticated
583     this.setCookie(obj.token);
584     this.olConnect = true;
585     this.startPolling();    
586     br.init();
587 }
588
589 OLAuth.prototype.callback = function(obj) {
590     if (false == obj.success) {
591         this.showPopup("#f00", "#fff", obj.msg);
592         clearInterval(this.poller);
593         this.ttsPoller = null;
594     } else {
595         this.olConnect = true;
596         this.setCookie(obj.token);
597     }
598 }
599
600 OLAuth.prototype.setCookie = function(value) {
601     var date = new Date();
602     date.setTime(date.getTime()+(24*60*60*1000));  //one day expiry
603     var expiry = date.toGMTString();
604     var cookie = 'loan-'+br.bookId+'='+value;
605     cookie    += '; expires='+expiry;
606     cookie    += '; path=/; domain=.archive.org;';
607     document.cookie = cookie; 
608 }
609
610 OLAuth.prototype.startPolling = function () {    
611     var self = this;
612     this.poller=setInterval(function(){
613         if (!self.olConnect) {
614           self.showPopup("#f00", "#fff", 'Cound not connect to Open Library for authentication. Please check to see if you are still connected to the Internet, and then reload this web page.');
615           clearInterval(self.poller);
616           self.ttsPoller = null;        
617         } else {
618           self.olConnect = false;
619           //be sure to add random param to authUrl to avoid stale cache
620           $.ajax({url:self.authUrl+'?rand='+Math.random(), dataType:'jsonp', jsonpCallback:'olAuth.callback'});
621         }
622     },300000);   
623 }
624
625 br.cleanupMetadata();
626 if (br.olAuth) {
627     var olAuth = new OLAuth();
628     olAuth.init();
629 } else {
630     br.init();
631 }
632 <?
633
634
635 function BRFatal($string) {
636     // $$$ TODO log error
637     echo "alert('$string')\n";
638     die(-1);
639 }
640
641 // Returns true if a page should be added based on it's information in
642 // the metadata
643 function shouldAddPage($page) {
644     // Return false only if the page is marked addToAccessFormats false.
645     // If there is no assertion we assume it should be added.
646     if (isset($page->addToAccessFormats)) {
647         if ("false" == strtolower(trim($page->addToAccessFormats))) {
648             return false;
649         }
650     }
651     
652     return true;
653 }
654
655 // Returns { 'imageFormat' => , 'archiveFormat' => '} given a sub-item prefix and loaded xml data
656 function findImageStack($subPrefix, $filesData) {
657
658     // $$$ The order of the image formats determines which will be returned first
659     $imageFormats = array('JP2' => 'jp2', 'TIFF' => 'tif', 'JPEG' => 'jpg');
660     $archiveFormats = array('ZIP' => 'zip', 'Tar' => 'tar');
661     $imageGroup = implode('|', array_keys($imageFormats));
662     $archiveGroup = implode('|', array_keys($archiveFormats));
663     // $$$ Currently only return processed images
664     $imageStackRegex = "/Single Page (Processed) (${imageGroup}) (${archiveGroup})/";
665         
666     foreach ($filesData->file as $file) {        
667         if (strpos($file['name'], $subPrefix) === 0) { // subprefix matches beginning
668             if (preg_match($imageStackRegex, $file->format, $matches)) {
669             
670                 // Make sure we have a regular image stack
671                 $imageFormat = $imageFormats[$matches[2]];
672                 if (strpos($file['name'], $subPrefix . '_' . $imageFormat) === 0) {            
673                     return array('imageFormat' => $imageFormat,
674                                  'archiveFormat' => $archiveFormats[$matches[3]],
675                                  'imageStackFile' => $file['name']);
676                 }
677             }
678         }
679     }
680     
681     return array('imageFormat' => 'unknown', 'archiveFormat' => 'unknown', 'imageStackFile' => 'unknown');
682         
683 }
684
685 ?>
686