Fix for images being returned overly large
[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', $protected = false, $isAdmin=false)
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.6";
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 <? if ($protected) { ?>
150     <link rel="stylesheet" type="text/css" href="/bookreader/BookReaderLending.css?v=<? echo($version); ?>">
151 <? } ?>
152     <script src="/includes/jquery-1.4.2.min.js" type="text/javascript"></script>
153     <script type="text/javascript" src="/bookreader/jquery-ui-1.8.5.custom.min.js?v=<? echo($version); ?>"></script>
154     <script type="text/javascript" src="http://www.archive.org/includes/analytics.js?v=2"></script>
155     <script type="text/javascript" src="/bookreader/dragscrollable.js?v=<? echo($version); ?>"></script>
156     <script type="text/javascript" src="/bookreader/jquery.colorbox-min.js"></script>
157     <script type="text/javascript" src="/bookreader/jquery.ui.ipad.js"></script>
158
159     <script type="text/javascript" src="/bookreader/jquery.bt.min.js"></script>
160     <script type="text/javascript" src="/bookreader/BookReader.js?v=<? echo($version); ?>"></script>
161 <? if ( !preg_match("/mobile/i", $_SERVER['HTTP_USER_AGENT']) ) { ?>
162     <script type="text/javascript" src="/bookreader/soundmanager/soundmanager2-ia.js?v=<? echo($version); ?>"></script>
163     <script>
164         soundManager.debugMode = false;
165         soundManager.url = '/bookreader/soundmanager/swf/';       
166         soundManager.useHTML5Audio = true;
167         soundManager.flashVersion = 9; //flash 8 version of swf is buggy when calling play() on a sound that is still loading
168     </script>
169 <? } /* mobile user agent */ ?>
170 </head>
171 <body style="background-color: ##939598;">
172
173 <div id="BookReader">
174     Internet Archive BookReader - <? echo $title; ?>
175     <br/>
176     
177     <noscript>
178     <p>
179         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>.
180     </p>
181     </noscript>
182 </div>
183
184 <script type="text/javascript">
185   // Set some config variables -- $$$ NB: Config object format has not been finalized
186   var brConfig = {};
187 <? if ($uiMode == 'embed') { ?>
188   brConfig["mode"] = 1;
189   brConfig["ui"] = "embed";
190 <? } else { ?>
191   brConfig["mode"] = 2;
192 <? } ?>
193 <? if ($isAdmin == true) {
194      echo '  brConfig["isAdmin"] = true;';
195    } ?>   
196 </script>
197 <script type="text/javascript">
198 // The URL in the script tag below is dynamically generated JavaScript that includes the book metadata and page image access functions.
199 // The ia{number}.us.archive.org server for the book can and does change, so this URL should NOT be used for permanent access.
200 // Use the JSLocate URL below instead for stable access - it will find the item and redirect to the correct server
201 // <? echo($locateURL); ?>
202
203 </script>
204 <script type="text/javascript" src="<? echo($metaURL); ?>"></script>
205
206 <script type="text/javascript">
207     // Usage stats
208     if(window.archive_analytics) { window.archive_analytics.values['bookreader'] = 'open'};
209 </script>
210 <!--<![endif]-->
211
212 <!--[if IE 7]>
213 <? BookReader::emitForIE7($server, $mainDir, $identifier, $subPrefix, $title, $coverLeaf, $titleStart, $uiMode); ?>
214 <![endif]-->
215
216
217 </body>
218 </html>
219   <?
220     exit;
221   }
222   
223   
224
225   
226   // Emit the HTML for the version of the BookReader for IE7
227   public static function emitForIE7($server, $mainDir, $identifier, $subPrefix, $title,
228                               $coverLeaf=null, $titleStart='Internet Archive', $uiMode='full')
229   {
230     // Set title to default if not set
231     if (!$title) {
232         $title = 'BookReader';
233     }
234     
235     $id = $identifier;
236     
237     // manually update with Launchpad version number at each checkin so that browsers
238     // do not use old cached version
239     // see https://bugs.launchpad.net/gnubook/+bug/330748
240     $version = "ie7";
241     
242     if (BookReader::getDevHost($server)) {
243         // on dev host - add time to force reload
244         $version .= '_' . time();
245     }
246     
247     if ("" == $id) {
248         echo "No identifier specified!";
249         die(-1);
250     }
251     
252     $metaURL = BookReader::jsMetadataURLForIE7($server, $identifier, $mainDir, $subPrefix);
253     
254     
255 ?>
256     <link rel="stylesheet" type="text/css" href="/bookreader/ie7/BookReader.css?v=<? echo($version); ?>">
257 <? if ($uiMode == "embed") { ?>
258     <link rel="stylesheet" type="text/css" href="/bookreader/ie7/BookReaderEmbed.css?v=<? echo($version); ?>">
259 <? } elseif ($uiMode == "touch") { ?>
260     <link rel="stylesheet" type="text/css" href="/bookreader/ie7/touch/BookReaderTouch.css?v=<? echo($version); ?>">
261 <? } /* uiMode */ ?>
262     <script src="/includes/jquery-1.4.2.min.js" type="text/javascript"></script>
263     <script type="text/javascript" src="http://www.archive.org/includes/analytics.js?v=2"></script>
264     <script type="text/javascript" src="/bookreader/ie7/jquery-ui-1.8.1.custom.min.js?v=<? echo($version); ?>"></script>
265     <script type="text/javascript" src="/bookreader/ie7/dragscrollable.js?v=<? echo($version); ?>"></script>
266     <script type="text/javascript" src="/bookreader/ie7/BookReader.js?v=<? echo($version); ?>"></script>
267 </head>
268 <body style="background-color: #FFFFFF;">
269
270 <? if ($uiMode == 'full') { ?>
271 <div id="BookReader" style="left:10px; right:200px; top:10px; bottom:2em;">Internet Archive BookReader <noscript>requires JavaScript to be enabled.</noscript></div>
272 <? } else { ?>
273 <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>
274 <? } /* uiMode*/ ?>
275
276 <script type="text/javascript">
277   // Set some config variables - $$$ NB: Config object format has not been finalized
278   var brConfig = {};
279 <? if ($uiMode == 'embed') { ?>
280   brConfig["mode"] = 1;
281   brConfig["reduce"] = 8;
282   brConfig["ui"] = "embed";
283 <? } else { ?>
284   brConfig["mode"] = 2;
285 <? } ?>
286 </script>
287 <script type="text/javascript" src="<? echo($metaURL); ?>"></script>
288
289 <? if ($uiMode == 'full') { ?>
290 <div id="BookReaderSearch" style="width:190px; right:0px; top:10px; bottom:2em;">
291     <form action='javascript:' onsubmit="br.search($('#BookReaderSearchBox').val());">
292         <p style="display: inline">
293             <input id="BookReaderSearchBox" type="text" size="20" value="search..." onfocus="if('search...'==this.value)this.value='';" /><input type="submit" value="go" />
294         </p>
295     </form>
296     <div id="BookReaderSearchResults">
297         Search results
298     </div>
299 </div>
300
301
302 <div id="BRfooter">
303     <div class="BRlogotype">
304         <a href="http://archive.org/" class="BRblack">Internet Archive</a>
305     </div>
306     <div class="BRnavlinks">
307         <a class="BRblack" href="http://www.archive.org/about/faqs.php#Report_Item">Content Problems</a> |
308         <a class="BRblack" href="https://bugs.launchpad.net/bookreader/+filebug">Report Bugs</a> |
309         <a class="BRblack" href="http://www.archive.org/details/texts">Texts Collection</a> |
310         <a class="BRblack" href="http://www.archive.org/about/contact.php">Contact Us</a>
311     </div>
312 </div>
313 <? } /* uiMode */ ?>
314
315 <script type="text/javascript">
316     // $$$ hack to workaround sizing bug when starting in two-up mode
317     $(document).ready(function() {
318         $(window).trigger('resize');
319     });
320     
321     // Usage stats
322     if(window.archive_analytics) { window.archive_analytics.values['bookreader'] = 'open'};
323 </script>
324   <?
325   }
326   
327   
328   // Returns the user part of dev host from URL, or null
329   public static function getDevHost($server)
330   {
331       if (preg_match("/^www-(\w+)/", $_SERVER["SERVER_NAME"], $match)) {
332         return $match[1];
333       }
334       
335       return null;
336   }
337
338   
339   public static function serverBaseURL($server)
340   {
341       // Check if we're on a dev vhost and point to JSIA in the user's public_html
342       // on the datanode
343       // $$$ the remapping isn't totally automatic yet and requires user to
344       //     ln -s ~/petabox/www/datanode/BookReader ~/public_html/BookReader
345       //     so we enable it only for known hosts
346       $devhost = BookReader::getDevHost($server);
347       $devhosts = array('mang', 'testflip', 'rkumar');
348       if (in_array($devhost, $devhosts)) {
349         $server = $server . "/~" . $devhost;
350       }
351       return $server;
352   }
353   
354   
355   public static function jsMetadataURL($server, $identifier, $mainDir, $subPrefix = '')
356   {
357     $serverBaseURL = BookReader::serverBaseURL($server);
358
359     $params = array( 'id' => $identifier, 'itemPath' => $mainDir, 'server' => $server );
360     if ($subPrefix) {
361         $params['subPrefix'] = $subPrefix;
362     }
363     
364     $keys = array_keys($params);
365     $lastParam = end($keys);
366     $url = "http://{$serverBaseURL}/BookReader/BookReaderJSIA.php?";
367     foreach($params as $param=>$value) {
368         $url .= $param . '=' . $value;
369         if ($param != $lastParam) {
370             $url .= '&';
371         }
372     }
373     
374     return $url;
375   }
376   
377     public static function jsMetadataURLForIE7($server, $identifier, $mainDir, $subPrefix = '')
378   {
379     $serverBaseURL = BookReader::serverBaseURL($server);
380
381     $params = array( 'id' => $identifier, 'itemPath' => $mainDir, 'server' => $server );
382     if ($subPrefix) {
383         $params['subPrefix'] = $subPrefix;
384     }
385     
386     $keys = array_keys($params);
387     $lastParam = end($keys);
388     $url = "http://{$serverBaseURL}/BookReader/ie7/BookReaderJSIA.php?";
389     foreach($params as $param=>$value) {
390         $url .= $param . '=' . $value;
391         if ($param != $lastParam) {
392             $url .= '&';
393         }
394     }
395     
396     return $url;
397   }
398
399   
400   // This returns a URL that finds the item then returns a redirect to BookReaderJSIA.php
401   // on the item's server.
402   public static function jsLocateURL($identifier, $subPrefix = '')
403   {
404     $locateURL = 'http://www.archive.org/bookreader/BookReaderJSLocate.php?id=' . $identifier;
405     if ($subPrefix) {
406         $locateURL .= '&subPrefix=' . $subPrefix;
407     }
408     return $locateURL;
409   }
410   
411   // Return the URL for the requested /download/$path, or null
412   public static function getURL($path, $item) {
413     // $path should look like {itemId}/{operator}/{filename}
414     // Other operators may be added
415     
416     $urlParts = BookReader::parsePath($path);
417     
418     // Check for non-handled cases
419     $required = array('identifier', 'operator', 'operand');
420     foreach ($required as $key) {
421         if (!array_key_exists($key, $urlParts)) {
422             return null;
423         }
424     }
425     
426     $identifier = $urlParts['identifier'];
427     $operator = $urlParts['operator'];
428     $filename = $urlParts['operand'];
429     $subPrefix = $urlParts['subPrefix'];
430     
431     $serverBaseURL = BookReader::serverBaseURL($item->getServer());
432     
433     // Baseline query params
434     $query = array(
435         'id' => $identifier,
436         'itemPath' => $item->getMainDir(),
437         'server' => $serverBaseURL
438     );
439     if ($subPrefix) {
440         $query['subPrefix'] = $subPrefix;
441     }
442     
443     switch ($operator) {
444         case 'page':
445             
446             // Look for old-style preview request - e.g. {identifier}_cover.jpg
447             if (preg_match('/^(.*)_((cover|title|preview).*)/', $filename, $matches) === 1) {
448                 // Serve preview image
449                 $page = $matches[2];
450                 $query['page'] = $page;
451                 return 'http://' . $serverBaseURL . '/BookReader/BookReaderPreview.php?' . http_build_query($query, '', '&');
452             }
453             
454             // New-style preview request - e.g. cover_thumb.jpg
455             if (preg_match('/^(cover|title|preview)/', $filename, $matches) === 1) {
456                 $query['page'] = $filename;
457                 return 'http://' . $serverBaseURL . '/BookReader/BookReaderPreview.php?' . http_build_query($query, '', '&');
458             }
459             
460             // Asking for a non-preview page
461             $query['page'] = $filename;
462             return 'http://' . $serverBaseURL . '/BookReader/BookReaderImages.php?' . http_build_query($query, '', '&');
463         
464         default:
465             // Unknown operator
466             return null;            
467     }
468       
469     return null; // was not handled
470   }
471   
472   public static function browserFromUserAgent($userAgent) {
473       $browserPatterns = array(
474           'ipad' => '/iPad/',
475           'iphone' => '/iPhone/', // Also cover iPod Touch
476           'android' => '/Android/',
477       );
478       
479       foreach ($browserPatterns as $browser => $pattern) {
480           if (preg_match($pattern, $userAgent)) {
481               return $browser;
482           }
483       }
484       return null;
485   }
486
487   
488   // $$$ Ideally we will not rely on user agent, but for the moment we do
489   public static function paramsFromUserAgent($userAgent) {
490       // $$$ using 'embed' here for devices with assumed small screens -- really should just use CSS3 media queries
491       $browserParams = array(
492           'ipad' => array( 'ui' => 'touch' ),
493           'iphone' => array( 'ui' => 'embed', 'mode' => '1up' ),
494           'android' => array( 'ui' => 'embed', 'mode' => '1up' ),
495       );
496   
497       $browser = BookReader::browserFromUserAgent($userAgent);
498       if ($browser) {
499           return $browserParams[$browser];
500       }
501       return array();
502   }
503   
504   public static function parsePath($path) {
505     // Parse the BookReader path and return the parts
506     // e.g. itemid/some/sub/dir/page/cover.jpg -> array( 'identifier' => 'itemid', 'subPrefix' => 'some/sub/dir',
507     //            'operator' => 'page', 'filename' => 'cover.jpg')
508     
509     $parts = array();
510     
511     // Pull off query, e.g. ?foo=bar
512     if (preg_match('#(.*?)(\?.*)#', $path, $matches) === 1) {
513         $parts['query'] = $matches[2];
514         $path = $matches[1];
515     }
516     
517     // Pull off identifier
518     if (preg_match('#[^/&?]+#', $path, $matches) === 0) {
519         // no match
520         return $parts;
521     }
522     $parts['identifier'] = $matches[0];
523     $path = substr($path, strlen($matches[0]));
524     
525     // Look for operators
526     // The sub-prefix can be arbitrary, so we match up until the first operator
527     $operators = '(' . join('|', self::$downloadOperators) . ')';
528     $pattern = '#(?P<subPrefix>.*?)/(?P<operator>' . $operators . ')/(?P<operand>.*)#';
529     if (preg_match($pattern, $path, $matches) === 1) {
530         $parts['subPrefix'] = substr($matches['subPrefix'], 1); // remove leading '/'
531         $parts['operator'] = $matches['operator'];
532         $parts['operand'] = $matches['operand'];
533     } else {
534         $parts['subPrefix'] = $path;
535     }
536     
537     return $parts;
538   }
539     
540 }
541
542 ?>