Add "Return this book" under the book's title if it's a borrowed book
[bookreader.git] / BookReaderIA / inc / BookReader.inc
1 <?
2
3 /*
4  * Copyright(c) 2008-2010 Internet Archive. Software license AGPL version 3.
5  *
6  * This file is part of BookReader.  The full source code can be found at GitHub:
7  * http://github.com/openlibrary/bookreader
8  *
9  * Note: Edits to this file must pass through github.  To submit a patch to this
10  *       file please contact mang via http://github.com/mangtronix or mang at archive dot org
11  *       Direct changes to this file may get clobbered when the code is synchronized
12  *       from github.
13  */
14
15 class BookReader
16 {
17
18
19   // Operators recognized in BookReader download URLs
20   public static $downloadOperators = array('page');
21
22   // Returns true if can display the book in item with a given prefix (typically the item identifier)
23   public static function canDisplay($item, $prefix, $checkOldScandata = false)
24   {
25     
26     // A "book" is an image stack and scandata.
27     // 1. Old items may have scandata.xml or scandata.zip and itemid_{imageformat}.{zip,tar}
28     // 2. Newer items may have multiple {arbitraryname}_scandata.xml and {arbitraryname}_{imageformat}.{zip,tar}
29         
30     $foundScandata = false;
31     $foundImageStack = false;
32     
33     $targetScandata = $prefix . "_scandata.xml";
34         
35     // $$$ TODO add support for jpg and tar stacks
36     // https://bugs.edge.launchpad.net/gnubook/+bug/323003
37     // https://bugs.edge.launchpad.net/gnubook/+bug/385397
38     $imageFormatRegex = '@' . preg_quote($prefix, '@') . '_(jp2|tif|jpg)\.(zip|tar)$@';
39     
40     $baseLength = strlen($item->metadataGrabber->mainDir . '/');
41     foreach ($item->getFiles() as $location => $fileInfo) {
42         $filename = substr($location, $baseLength);
43         
44         if ($checkOldScandata) {
45             if ($filename == 'scandata.xml' || $filename == 'scandata.zip') {
46                 $foundScandata = $filename;
47             }
48         }
49         
50         if ($filename == $targetScandata) {
51             $foundScandata = $filename;
52         }
53         
54         if (preg_match($imageFormatRegex, $filename)) {
55             $foundImageStack = $filename;
56         }
57     }
58     
59     if ($foundScandata && $foundImageStack) {
60         return true;
61     }
62     
63     return false;
64   }
65   
66   // Finds the prefix to use for the book given the part of the URL trailing after /stream/
67   public static function findPrefix($urlPortion)
68   {
69     if (!preg_match('#[^/&?]+#', $urlPortion, $matches)) {
70         // URL portion was empty or started with /, &, or ? -- no item identifier
71         return false;
72     }
73     
74     $prefix = $matches[0]; // item identifier
75     
76     // $$$ Currently swallows the rest of the URL.
77     //     If we want to support e.g. /stream/itemid/subdir/prefix/page/23 will need to adjust.
78     if (preg_match('#[^/&?]+/([^&?]+)#', $urlPortion, $matches)) {
79         // Match is everything after item identifier and slash, up to end or ? or &
80         // e.g. itemid/{match/these/parts}?foo=bar
81         $prefix = $matches[1]; // sub prefix -- 
82     }
83     
84     return $prefix;
85   }
86
87   // $$$ would be cleaner to use different templates instead of the uiMode param
88   // 
89   // @param subprefix Optional prefix to display a book inside an item (e.g. if does not match identifier)
90   public static function draw($server, $mainDir, $identifier, $subPrefix, $title,
91                               $coverLeaf=null, $titleStart='Internet Archive', $uiMode='full')
92   {
93     // Set title to default if not set
94     if (!$title) {
95         $title = 'BookReader';
96     }
97     
98     $id = $identifier;
99     
100     // manually update with Launchpad version number at each checkin so that browsers
101     // do not use old cached version
102     // see https://bugs.launchpad.net/gnubook/+bug/330748
103     $version = "3.0.1";
104     
105     if (BookReader::getDevHost($server)) {
106         // On dev host - add time to force reload
107         // If debugging on IE, remove this line otherwise breakpoints will be invalid after reload
108         $version .= '_' . time();
109     }
110     
111     if ("" == $id) {
112         echo "No identifier specified!";
113         die(-1);
114     }
115     
116     $metaURL = BookReader::jsMetadataURL($server, $identifier, $mainDir, $subPrefix);
117     $metaURL .= "&version=" . $version;
118     $locateURL = BookReader::jsLocateURL($identifier, $subPrefix);
119     $coverThumb = 'http://www.archive.org/download/' . $identifier . '/'. $subPrefix . '/page/cover_w114.jpg';
120     // startup-up-image must be exactly 320x460
121     //$startupImage = 'http://www.archive.org/download/' . $identifier . '/'. $subPrefix . '/page/cover_w512.jpg';
122     
123 ?>
124 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
125 <html>
126 <head>
127     <meta name="viewport" content="width=device-width, maximum-scale=1.0" />
128     <meta name="apple-mobile-web-app-capable" content="yes" />
129     <meta name="apple-mobile-web-app-status-bar-style" content="black" />
130     <link rel="apple-touch-icon" href="<? echo($coverThumb); ?>" />
131     <title><? echo $title; ?></title>
132     
133     <!--[if lte IE 6]>
134     <meta http-equiv="refresh" content="2; URL=/bookreader/browserunsupported.php?id=<? echo($id); ?>">
135     <![endif]-->
136
137     <!-- THIS ALLOWS BEAUTYTIPS TO WORK ON IE -->
138     <!--[if lt IE 9]>
139     <script type="text/javascript" src="/includes/excanvas.compiled.js"></script>
140     <![endif]-->
141
142 <!--[if !IE 7]><![IGNORE[--><![IGNORE[]]>
143     <link rel="stylesheet" type="text/css" href="/bookreader/BookReader.css?v=<? echo($version); ?>">
144 <? if ($uiMode == "embed") { ?>
145     <link rel="stylesheet" type="text/css" href="/bookreader/BookReaderEmbed.css?v=<? echo($version); ?>">
146 <? } elseif ($uiMode == "touch") { ?>
147     <link rel="stylesheet" type="text/css" href="/bookreader/touch/BookReaderTouch.css?v=<? echo($version); ?>">
148 <? } /* uiMode */ ?>
149     <script src="/includes/jquery-1.4.2.min.js" type="text/javascript"></script>
150     <script type="text/javascript" src="/bookreader/jquery-ui-1.8.5.custom.min.js?v=<? echo($version); ?>"></script>
151     <script type="text/javascript" src="http://www.archive.org/includes/analytics.js?v=2"></script>
152     <script type="text/javascript" src="/bookreader/dragscrollable.js?v=<? echo($version); ?>"></script>
153     <script type="text/javascript" src="/bookreader/jquery.colorbox-min.js"></script>
154     <script type="text/javascript" src="/bookreader/jquery.ui.ipad.js"></script>
155
156     <script type="text/javascript" src="/bookreader/jquery.bt.min.js"></script>
157     <script type="text/javascript" src="/bookreader/BookReader.js?v=<? echo($version); ?>"></script>
158 <? if ( !preg_match("/mobile/i", $_SERVER['HTTP_USER_AGENT']) ) { ?>
159     <script type="text/javascript" src="/bookreader/soundmanager/soundmanager2-ia.js?v=<? echo($version); ?>"></script>
160     <script>
161         soundManager.debugMode = false;
162         soundManager.url = '/bookreader/soundmanager/swf/';       
163         soundManager.useHTML5Audio = true;
164         soundManager.flashVersion = 9; //flash 8 version of swf is buggy when calling play() on a sound that is still loading
165     </script>
166 <? } /* mobile user agent */ ?>
167 </head>
168 <body style="background-color: ##939598;">
169
170 <div id="BookReader">
171     Internet Archive BookReader - <? echo $title; ?>
172     <br/>
173     
174     <noscript>
175     <p>
176         The BookReader requires JavaScript to be enabled. Please check that your browser supports JavaScript and that it is enabled in the browser settings.  You can also try one of the <a href="http://www.archive.org/details/<? echo $identifier; ?>"> other formats of the book</a>.
177     </p>
178     </noscript>
179 </div>
180
181 <script type="text/javascript">
182   // Set some config variables -- $$$ NB: Config object format has not been finalized
183   var brConfig = {};
184 <? if ($uiMode == 'embed') { ?>
185   brConfig["mode"] = 1;
186   brConfig["ui"] = "embed";
187 <? } else { ?>
188   brConfig["mode"] = 2;
189 <? } ?>
190 </script>
191 <script type="text/javascript">
192 // The URL in the script tag below is dynamically generated JavaScript that includes the book metadata and page image access functions.
193 // The ia{number}.us.archive.org server for the book can and does change, so this URL should NOT be used for permanent access.
194 // Use the JSLocate URL below instead for stable access - it will find the item and redirect to the correct server
195 // <? echo($locateURL); ?>
196
197 </script>
198 <script type="text/javascript" src="<? echo($metaURL); ?>"></script>
199
200 <script type="text/javascript">
201     // Usage stats
202     if(window.archive_analytics) { window.archive_analytics.values['bookreader'] = 'open'};
203 </script>
204 <!--<![endif]-->
205
206 <!--[if IE 7]>
207 <? BookReader::emitForIE7($server, $mainDir, $identifier, $subPrefix, $title, $coverLeaf, $titleStart, $uiMode); ?>
208 <![endif]-->
209
210
211 </body>
212 </html>
213   <?
214     exit;
215   }
216   
217   
218
219   
220   // Emit the HTML for the version of the BookReader for IE7
221   public static function emitForIE7($server, $mainDir, $identifier, $subPrefix, $title,
222                               $coverLeaf=null, $titleStart='Internet Archive', $uiMode='full')
223   {
224     // Set title to default if not set
225     if (!$title) {
226         $title = 'BookReader';
227     }
228     
229     $id = $identifier;
230     
231     // manually update with Launchpad version number at each checkin so that browsers
232     // do not use old cached version
233     // see https://bugs.launchpad.net/gnubook/+bug/330748
234     $version = "ie7";
235     
236     if (BookReader::getDevHost($server)) {
237         // on dev host - add time to force reload
238         $version .= '_' . time();
239     }
240     
241     if ("" == $id) {
242         echo "No identifier specified!";
243         die(-1);
244     }
245     
246     $metaURL = BookReader::jsMetadataURLForIE7($server, $identifier, $mainDir, $subPrefix);
247     
248     
249 ?>
250     <link rel="stylesheet" type="text/css" href="/bookreader/ie7/BookReader.css?v=<? echo($version); ?>">
251 <? if ($uiMode == "embed") { ?>
252     <link rel="stylesheet" type="text/css" href="/bookreader/ie7/BookReaderEmbed.css?v=<? echo($version); ?>">
253 <? } elseif ($uiMode == "touch") { ?>
254     <link rel="stylesheet" type="text/css" href="/bookreader/ie7/touch/BookReaderTouch.css?v=<? echo($version); ?>">
255 <? } /* uiMode */ ?>
256     <script src="/includes/jquery-1.4.2.min.js" type="text/javascript"></script>
257     <script type="text/javascript" src="http://www.archive.org/includes/analytics.js?v=2"></script>
258     <script type="text/javascript" src="/bookreader/ie7/jquery-ui-1.8.1.custom.min.js?v=<? echo($version); ?>"></script>
259     <script type="text/javascript" src="/bookreader/ie7/dragscrollable.js?v=<? echo($version); ?>"></script>
260     <script type="text/javascript" src="/bookreader/ie7/BookReader.js?v=<? echo($version); ?>"></script>
261 </head>
262 <body style="background-color: #FFFFFF;">
263
264 <? if ($uiMode == 'full') { ?>
265 <div id="BookReader" style="left:10px; right:200px; top:10px; bottom:2em;">Internet Archive BookReader <noscript>requires JavaScript to be enabled.</noscript></div>
266 <? } else { ?>
267 <div id="BookReader" style="left:0; right:0; top:0; bottom:0; border:0">Internet Archive Bookreader <noscript>requires JavaScript to be enabled.</noscript></div>
268 <? } /* uiMode*/ ?>
269
270 <script type="text/javascript">
271   // Set some config variables - $$$ NB: Config object format has not been finalized
272   var brConfig = {};
273 <? if ($uiMode == 'embed') { ?>
274   brConfig["mode"] = 1;
275   brConfig["reduce"] = 8;
276   brConfig["ui"] = "embed";
277 <? } else { ?>
278   brConfig["mode"] = 2;
279 <? } ?>
280 </script>
281 <script type="text/javascript" src="<? echo($metaURL); ?>"></script>
282
283 <? if ($uiMode == 'full') { ?>
284 <div id="BookReaderSearch" style="width:190px; right:0px; top:10px; bottom:2em;">
285     <form action='javascript:' onsubmit="br.search($('#BookReaderSearchBox').val());">
286         <p style="display: inline">
287             <input id="BookReaderSearchBox" type="text" size="20" value="search..." onfocus="if('search...'==this.value)this.value='';" /><input type="submit" value="go" />
288         </p>
289     </form>
290     <div id="BookReaderSearchResults">
291         Search results
292     </div>
293 </div>
294
295
296 <div id="BRfooter">
297     <div class="BRlogotype">
298         <a href="http://archive.org/" class="BRblack">Internet Archive</a>
299     </div>
300     <div class="BRnavlinks">
301         <a class="BRblack" href="http://www.archive.org/about/faqs.php#Report_Item">Content Problems</a> |
302         <a class="BRblack" href="https://bugs.launchpad.net/bookreader/+filebug">Report Bugs</a> |
303         <a class="BRblack" href="http://www.archive.org/details/texts">Texts Collection</a> |
304         <a class="BRblack" href="http://www.archive.org/about/contact.php">Contact Us</a>
305     </div>
306 </div>
307 <? } /* uiMode */ ?>
308
309 <script type="text/javascript">
310     // $$$ hack to workaround sizing bug when starting in two-up mode
311     $(document).ready(function() {
312         $(window).trigger('resize');
313     });
314     
315     // Usage stats
316     if(window.archive_analytics) { window.archive_analytics.values['bookreader'] = 'open'};
317 </script>
318   <?
319   }
320   
321   
322   // Returns the user part of dev host from URL, or null
323   public static function getDevHost($server)
324   {
325       if (preg_match("/^www-(\w+)/", $_SERVER["SERVER_NAME"], $match)) {
326         return $match[1];
327       }
328       
329       return null;
330   }
331
332   
333   public static function serverBaseURL($server)
334   {
335       // Check if we're on a dev vhost and point to JSIA in the user's public_html
336       // on the datanode
337       // $$$ the remapping isn't totally automatic yet and requires user to
338       //     ln -s ~/petabox/www/datanode/BookReader ~/public_html/BookReader
339       //     so we enable it only for known hosts
340       $devhost = BookReader::getDevHost($server);
341       $devhosts = array('mang', 'testflip', 'rkumar');
342       if (in_array($devhost, $devhosts)) {
343         $server = $server . "/~" . $devhost;
344       }
345       return $server;
346   }
347   
348   
349   public static function jsMetadataURL($server, $identifier, $mainDir, $subPrefix = '')
350   {
351     $serverBaseURL = BookReader::serverBaseURL($server);
352
353     $params = array( 'id' => $identifier, 'itemPath' => $mainDir, 'server' => $server );
354     if ($subPrefix) {
355         $params['subPrefix'] = $subPrefix;
356     }
357     
358     $keys = array_keys($params);
359     $lastParam = end($keys);
360     $url = "http://{$serverBaseURL}/BookReader/BookReaderJSIA.php?";
361     foreach($params as $param=>$value) {
362         $url .= $param . '=' . $value;
363         if ($param != $lastParam) {
364             $url .= '&';
365         }
366     }
367     
368     return $url;
369   }
370   
371     public static function jsMetadataURLForIE7($server, $identifier, $mainDir, $subPrefix = '')
372   {
373     $serverBaseURL = BookReader::serverBaseURL($server);
374
375     $params = array( 'id' => $identifier, 'itemPath' => $mainDir, 'server' => $server );
376     if ($subPrefix) {
377         $params['subPrefix'] = $subPrefix;
378     }
379     
380     $keys = array_keys($params);
381     $lastParam = end($keys);
382     $url = "http://{$serverBaseURL}/BookReader/ie7/BookReaderJSIA.php?";
383     foreach($params as $param=>$value) {
384         $url .= $param . '=' . $value;
385         if ($param != $lastParam) {
386             $url .= '&';
387         }
388     }
389     
390     return $url;
391   }
392
393   
394   // This returns a URL that finds the item then returns a redirect to BookReaderJSIA.php
395   // on the item's server.
396   public static function jsLocateURL($identifier, $subPrefix = '')
397   {
398     $locateURL = 'http://www.archive.org/bookreader/BookReaderJSLocate.php?id=' . $identifier;
399     if ($subPrefix) {
400         $locateURL .= '&subPrefix=' . $subPrefix;
401     }
402     return $locateURL;
403   }
404   
405   // Return the URL for the requested /download/$path, or null
406   public static function getURL($path, $item) {
407     // $path should look like {itemId}/{operator}/{filename}
408     // Other operators may be added
409     
410     $urlParts = BookReader::parsePath($path);
411     
412     // Check for non-handled cases
413     $required = array('identifier', 'operator', 'operand');
414     foreach ($required as $key) {
415         if (!array_key_exists($key, $urlParts)) {
416             return null;
417         }
418     }
419     
420     $identifier = $urlParts['identifier'];
421     $operator = $urlParts['operator'];
422     $filename = $urlParts['operand'];
423     $subPrefix = $urlParts['subPrefix'];
424     
425     $serverBaseURL = BookReader::serverBaseURL($item->getServer());
426     
427     // Baseline query params
428     $query = array(
429         'id' => $identifier,
430         'itemPath' => $item->getMainDir(),
431         'server' => $serverBaseURL
432     );
433     if ($subPrefix) {
434         $query['subPrefix'] = $subPrefix;
435     }
436     
437     switch ($operator) {
438         case 'page':
439             
440             // Look for old-style preview request - e.g. {identifier}_cover.jpg
441             if (preg_match('/^(.*)_((cover|title|preview).*)/', $filename, $matches) === 1) {
442                 // Serve preview image
443                 $page = $matches[2];
444                 $query['page'] = $page;
445                 return 'http://' . $serverBaseURL . '/BookReader/BookReaderPreview.php?' . http_build_query($query, '', '&');
446             }
447             
448             // New-style preview request - e.g. cover_thumb.jpg
449             if (preg_match('/^(cover|title|preview)/', $filename, $matches) === 1) {
450                 $query['page'] = $filename;
451                 return 'http://' . $serverBaseURL . '/BookReader/BookReaderPreview.php?' . http_build_query($query, '', '&');
452             }
453             
454             // Asking for a non-preview page
455             $query['page'] = $filename;
456             return 'http://' . $serverBaseURL . '/BookReader/BookReaderImages.php?' . http_build_query($query, '', '&');
457         
458         default:
459             // Unknown operator
460             return null;            
461     }
462       
463     return null; // was not handled
464   }
465   
466   public static function browserFromUserAgent($userAgent) {
467       $browserPatterns = array(
468           'ipad' => '/iPad/',
469           'iphone' => '/iPhone/', // Also cover iPod Touch
470           'android' => '/Android/',
471       );
472       
473       foreach ($browserPatterns as $browser => $pattern) {
474           if (preg_match($pattern, $userAgent)) {
475               return $browser;
476           }
477       }
478       return null;
479   }
480
481   
482   // $$$ Ideally we will not rely on user agent, but for the moment we do
483   public static function paramsFromUserAgent($userAgent) {
484       // $$$ using 'embed' here for devices with assumed small screens -- really should just use CSS3 media queries
485       $browserParams = array(
486           'ipad' => array( 'ui' => 'touch' ),
487           'iphone' => array( 'ui' => 'embed', 'mode' => '1up' ),
488           'android' => array( 'ui' => 'embed', 'mode' => '1up' ),
489       );
490   
491       $browser = BookReader::browserFromUserAgent($userAgent);
492       if ($browser) {
493           return $browserParams[$browser];
494       }
495       return array();
496   }
497   
498   public static function parsePath($path) {
499     // Parse the BookReader path and return the parts
500     // e.g. itemid/some/sub/dir/page/cover.jpg -> array( 'identifier' => 'itemid', 'subPrefix' => 'some/sub/dir',
501     //            'operator' => 'page', 'filename' => 'cover.jpg')
502     
503     $parts = array();
504     
505     // Pull off query, e.g. ?foo=bar
506     if (preg_match('#(.*?)(\?.*)#', $path, $matches) === 1) {
507         $parts['query'] = $matches[2];
508         $path = $matches[1];
509     }
510     
511     // Pull off identifier
512     if (preg_match('#[^/&?]+#', $path, $matches) === 0) {
513         // no match
514         return $parts;
515     }
516     $parts['identifier'] = $matches[0];
517     $path = substr($path, strlen($matches[0]));
518     
519     // Look for operators
520     // The sub-prefix can be arbitrary, so we match up until the first operator
521     $operators = '(' . join('|', self::$downloadOperators) . ')';
522     $pattern = '#(?P<subPrefix>.*?)/(?P<operator>' . $operators . ')/(?P<operand>.*)#';
523     if (preg_match($pattern, $path, $matches) === 1) {
524         $parts['subPrefix'] = substr($matches['subPrefix'], 1); // remove leading '/'
525         $parts['operator'] = $matches['operator'];
526         $parts['operand'] = $matches['operand'];
527     } else {
528         $parts['subPrefix'] = $path;
529     }
530     
531     return $parts;
532   }
533     
534 }
535
536 ?>