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