Reduce jumping/resizing in info modal when cover is loading
[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   // Operators recognized in BookReader download URLs
19   public static $downloadOperators = array('page');
20
21   // Returns true if can display the book in item with a given prefix (typically the item identifier)
22   public static function canDisplay($item, $prefix, $checkOldScandata = false)
23   {
24     
25     // A "book" is an image stack and scandata.
26     // 1. Old items may have scandata.xml or scandata.zip and itemid_{imageformat}.{zip,tar}
27     // 2. Newer items may have multiple {arbitraryname}_scandata.xml and {arbitraryname}_{imageformat}.{zip,tar}
28         
29     $foundScandata = false;
30     $foundImageStack = false;
31     
32     $targetScandata = $prefix . "_scandata.xml";
33         
34     // $$$ TODO add support for jpg and tar stacks
35     // https://bugs.edge.launchpad.net/gnubook/+bug/323003
36     // https://bugs.edge.launchpad.net/gnubook/+bug/385397
37     $imageFormatRegex = '@' . preg_quote($prefix, '@') . '_(jp2|tif|jpg)\.(zip|tar)$@';
38     
39     $baseLength = strlen($item->metadataGrabber->mainDir . '/');
40     foreach ($item->getFiles() as $location => $fileInfo) {
41         $filename = substr($location, $baseLength);
42         
43         if ($checkOldScandata) {
44             if ($filename == 'scandata.xml' || $filename == 'scandata.zip') {
45                 $foundScandata = $filename;
46             }
47         }
48         
49         if ($filename == $targetScandata) {
50             $foundScandata = $filename;
51         }
52         
53         if (preg_match($imageFormatRegex, $filename)) {
54             $foundImageStack = $filename;
55         }
56     }
57     
58     if ($foundScandata && $foundImageStack) {
59         return true;
60     }
61     
62     return false;
63   }
64   
65   // Finds the prefix to use for the book given the part of the URL trailing after /stream/
66   public static function findPrefix($urlPortion)
67   {
68     if (!preg_match('#[^/&?]+#', $urlPortion, $matches)) {
69         // URL portion was empty or started with /, &, or ? -- no item identifier
70         return false;
71     }
72     
73     $prefix = $matches[0]; // item identifier
74     
75     // $$$ Currently swallows the rest of the URL.
76     //     If we want to support e.g. /stream/itemid/subdir/prefix/page/23 will need to adjust.
77     if (preg_match('#[^/&?]+/([^&?]+)#', $urlPortion, $matches)) {
78         // Match is everything after item identifier and slash, up to end or ? or &
79         // e.g. itemid/{match/these/parts}?foo=bar
80         $prefix = $matches[1]; // sub prefix -- 
81     }
82     
83     return $prefix;
84   }
85
86   // $$$ would be cleaner to use different templates instead of the uiMode param
87   // 
88   // @param subprefix Optional prefix to display a book inside an item (e.g. if does not match identifier)
89   public static function draw($server, $mainDir, $identifier, $subPrefix, $title,
90                               $coverLeaf=null, $titleStart='Internet Archive', $uiMode='full')
91   {
92     // Set title to default if not set
93     if (!$title) {
94         $title = 'BookReader';
95     }
96     
97     $id = $identifier;
98     
99     // manually update with Launchpad version number at each checkin so that browsers
100     // do not use old cached version
101     // see https://bugs.launchpad.net/gnubook/+bug/330748
102     $version = "r28";
103     
104     if (BookReader::getDevHost($server)) {
105         // On dev host - add time to force reload
106         // If debugging on IE, remove this line otherwise breakpoints will be invalid after reload
107         $version .= '_' . time();
108     }
109     
110     if ("" == $id) {
111         echo "No identifier specified!";
112         die(-1);
113     }
114     
115     $metaURL = BookReader::jsMetadataURL($server, $identifier, $mainDir, $subPrefix);
116     $locateURL = BookReader::jsLocateURL($identifier, $subPrefix);
117     $coverThumb = 'http://www.archive.org/download/' . $identifier . '/'. $subPrefix . '/page/cover_w114.jpg';
118     // startup-up-image must be exactly 320x460
119     //$startupImage = 'http://www.archive.org/download/' . $identifier . '/'. $subPrefix . '/page/cover_w512.jpg';
120     
121 ?>
122 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
123 <html>
124 <head>
125     <meta name="viewport" content="width=device-width, maximum-scale=1.0" />
126     <meta name="apple-mobile-web-app-capable" content="yes" />
127     <meta name="apple-mobile-web-app-status-bar-style" content="black" />
128     <link rel="apple-touch-icon" href="<? echo($coverThumb); ?>" />
129     <title><? echo $title; ?></title>
130 <!--[if lte IE 6]>
131     <meta http-equiv="refresh" content="2; URL=/bookreader/browserunsupported.php?id=<? echo($id); ?>">
132 <![endif]-->
133     <link rel="stylesheet" type="text/css" href="/bookreader/BookReader.css?v=<? echo($version); ?>">
134 <? if ($uiMode == "embed") { ?>
135     <link rel="stylesheet" type="text/css" href="/bookreader/BookReaderEmbed.css?v=<? echo($version); ?>">
136 <? } elseif ($uiMode == "touch") { ?>
137     <link rel="stylesheet" type="text/css" href="/bookreader/touch/BookReaderTouch.css?v=<? echo($version); ?>">
138 <? } /* uiMode */ ?>
139     <script src="/includes/jquery-1.4.2.min.js" type="text/javascript"></script>
140     <script type="text/javascript" src="/bookreader/jquery-ui-1.8.5.custom.min.js?v=<? echo($version); ?>"></script>
141     <script type="text/javascript" src="http://www.archive.org/includes/analytics.js?v=2"></script>
142     <script type="text/javascript" src="/bookreader/dragscrollable.js?v=<? echo($version); ?>"></script>
143     <script type="text/javascript" src="/bookreader/jquery.colorbox-min.js"></script>
144     <script type="text/javascript" src="/bookreader/jquery.ui.ipad.js"></script>
145      <!-- THIS ALLOWS BEAUTYTIPS TO WORK ON IE -->
146         <!--[if lt IE 9]>
147         <script type="text/javascript" src="/includes/excanvas.compiled.js"></script>
148         <![endif]-->
149     <script type="text/javascript" src="/bookreader/jquery.bt.min.js"></script>
150     <script type="text/javascript" src="/bookreader/BookReader.js?v=<? echo($version); ?>"></script>
151 <? if ( !preg_match("/mobile/i", $_SERVER['HTTP_USER_AGENT']) ) { ?>
152     <script type="text/javascript" src="/bookreader/soundmanager/soundmanager2-ia.js?v=<? echo($version); ?>"></script>
153     <script>
154         soundManager.debugMode = false;
155         soundManager.url = '/bookreader/soundmanager/swf/';       
156         soundManager.useHTML5Audio = true;
157         soundManager.flashVersion = 9; //flash 8 version of swf is buggy when calling play() on a sound that is still loading
158     </script>
159 <? } /* mobile user agent */ ?>
160 </head>
161 <body style="background-color: ##939598;">
162
163 <?
164 /*
165 // <? if ($uiMode == 'full') { ?>
166 // <div id="BookReader" style="left:10px; right:200px; top:10px; bottom:2em;">Internet Archive BookReader <noscript>requires JavaScript to be enabled.</noscript></div>
167 // <? } else { ?>
168 // <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>
169 // <? } ?>
170 */
171 ?>
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 <!-- The script included below is dynamically generated JavaScript that includes the book metadata and page image access functions.
195      The ia{number}.us.archive.org server referenced below can and does change, so this URL should NOT be used for permanent access.  -->
196 <script type="text/javascript" src="<? echo($metaURL); ?>"></script>
197 <!-- This URL will find the item and redirect to the correct server.  Remove the line above and use the URL below for stable access. -->
198 <!-- <script type="text/javascript" src="<? echo($locateURL); ?>"></script> -->
199
200 <script type="text/javascript">
201     // Usage stats
202     if(window.archive_analytics) { window.archive_analytics.values['bookreader'] = 'open'};
203 </script>
204   <?
205     exit;
206   }
207   
208   // Returns the user part of dev host from URL, or null
209   public static function getDevHost($server)
210   {
211       if (preg_match("/^www-(\w+)/", $_SERVER["SERVER_NAME"], $match)) {
212         return $match[1];
213       }
214       
215       return null;
216   }
217
218   
219   public static function serverBaseURL($server)
220   {
221       // Check if we're on a dev vhost and point to JSIA in the user's public_html
222       // on the datanode
223       // $$$ the remapping isn't totally automatic yet and requires user to
224       //     ln -s ~/petabox/www/datanode/BookReader ~/public_html/BookReader
225       //     so we enable it only for known hosts
226       $devhost = BookReader::getDevHost($server);
227       $devhosts = array('mang', 'testflip', 'rkumar');
228       if (in_array($devhost, $devhosts)) {
229         $server = $server . "/~" . $devhost;
230       }
231       return $server;
232   }
233   
234   
235   public static function jsMetadataURL($server, $identifier, $mainDir, $subPrefix = '')
236   {
237     $serverBaseURL = BookReader::serverBaseURL($server);
238
239     $params = array( 'id' => $identifier, 'itemPath' => $mainDir, 'server' => $server );
240     if ($subPrefix) {
241         $params['subPrefix'] = $subPrefix;
242     }
243     
244     $keys = array_keys($params);
245     $lastParam = end($keys);
246     $url = "http://{$serverBaseURL}/BookReader/BookReaderJSIA.php?";
247     foreach($params as $param=>$value) {
248         $url .= $param . '=' . $value;
249         if ($param != $lastParam) {
250             $url .= '&';
251         }
252     }
253     
254     return $url;
255   }
256   
257   // This returns a URL that finds the item then returns a redirect to BookReaderJSIA.php
258   // on the item's server.
259   public static function jsLocateURL($identifier, $subPrefix = '')
260   {
261     $locateURL = 'http://www.archive.org/bookreader/BookReaderJSLocate.php?id=' . $identifier;
262     if ($subPrefix) {
263         $locateURL .= '&subPrefix=' . $subPrefix;
264     }
265     return $locateURL;
266   }
267   
268   // Return the URL for the requested /download/$path, or null
269   public static function getURL($path, $item) {
270     // $path should look like {itemId}/{operator}/{filename}
271     // Other operators may be added
272     
273     $urlParts = BookReader::parsePath($path);
274     
275     // Check for non-handled cases
276     $required = array('identifier', 'operator', 'operand');
277     foreach ($required as $key) {
278         if (!array_key_exists($key, $urlParts)) {
279             return null;
280         }
281     }
282     
283     $identifier = $urlParts['identifier'];
284     $operator = $urlParts['operator'];
285     $filename = $urlParts['operand'];
286     $subPrefix = $urlParts['subPrefix'];
287     
288     $serverBaseURL = BookReader::serverBaseURL($item->getServer());
289     
290     // Baseline query params
291     $query = array(
292         'id' => $identifier,
293         'itemPath' => $item->getMainDir(),
294         'server' => $serverBaseURL
295     );
296     if ($subPrefix) {
297         $query['subPrefix'] = $subPrefix;
298     }
299     
300     switch ($operator) {
301         case 'page':
302             
303             // Look for old-style preview request - e.g. {identifier}_cover.jpg
304             if (preg_match('/^(.*)_((cover|title|preview).*)/', $filename, $matches) === 1) {
305                 // Serve preview image
306                 $page = $matches[2];
307                 $query['page'] = $page;
308                 return 'http://' . $serverBaseURL . '/BookReader/BookReaderPreview.php?' . http_build_query($query, '', '&');
309             }
310             
311             // New-style preview request - e.g. cover_thumb.jpg
312             if (preg_match('/^(cover|title|preview)/', $filename, $matches) === 1) {
313                 $query['page'] = $filename;
314                 return 'http://' . $serverBaseURL . '/BookReader/BookReaderPreview.php?' . http_build_query($query, '', '&');
315             }
316             
317             // Asking for a non-preview page
318             $query['page'] = $filename;
319             return 'http://' . $serverBaseURL . '/BookReader/BookReaderImages.php?' . http_build_query($query, '', '&');
320         
321         default:
322             // Unknown operator
323             return null;            
324     }
325       
326     return null; // was not handled
327   }
328   
329   public static function browserFromUserAgent($userAgent) {
330       $browserPatterns = array(
331           'ipad' => '/iPad/',
332           'iphone' => '/iPhone/', // Also cover iPod Touch
333           'android' => '/Android/',
334       );
335       
336       foreach ($browserPatterns as $browser => $pattern) {
337           if (preg_match($pattern, $userAgent)) {
338               return $browser;
339           }
340       }
341       return null;
342   }
343
344   
345   // $$$ Ideally we will not rely on user agent, but for the moment we do
346   public static function paramsFromUserAgent($userAgent) {
347       // $$$ using 'embed' here for devices with assumed small screens -- really should just use CSS3 media queries
348       $browserParams = array(
349           'ipad' => array( 'ui' => 'touch' ),
350           'iphone' => array( 'ui' => 'embed', 'mode' => '1up' ),
351           'android' => array( 'ui' => 'embed', 'mode' => '1up' ),
352       );
353   
354       $browser = BookReader::browserFromUserAgent($userAgent);
355       if ($browser) {
356           return $browserParams[$browser];
357       }
358       return array();
359   }
360   
361   public static function parsePath($path) {
362     // Parse the BookReader path and return the parts
363     // e.g. itemid/some/sub/dir/page/cover.jpg -> array( 'identifier' => 'itemid', 'subPrefix' => 'some/sub/dir',
364     //            'operator' => 'page', 'filename' => 'cover.jpg')
365     
366     $parts = array();
367     
368     // Pull off query, e.g. ?foo=bar
369     if (preg_match('#(.*?)(\?.*)#', $path, $matches) === 1) {
370         $parts['query'] = $matches[2];
371         $path = $matches[1];
372     }
373     
374     // Pull off identifier
375     if (preg_match('#[^/&?]+#', $path, $matches) === 0) {
376         // no match
377         return $parts;
378     }
379     $parts['identifier'] = $matches[0];
380     $path = substr($path, strlen($matches[0]));
381     
382     // Look for operators
383     // The sub-prefix can be arbitrary, so we match up until the first operator
384     $operators = '(' . join('|', self::$downloadOperators) . ')';
385     $pattern = '#(?P<subPrefix>.*?)/(?P<operator>' . $operators . ')/(?P<operand>.*)#';
386     if (preg_match($pattern, $path, $matches) === 1) {
387         $parts['subPrefix'] = substr($matches['subPrefix'], 1); // remove leading '/'
388         $parts['operator'] = $matches['operator'];
389         $parts['operand'] = $matches['operand'];
390     } else {
391         $parts['subPrefix'] = $path;
392     }
393     
394     return $parts;
395   }
396     
397 }
398
399 ?>