4 * Copyright(c) 2008-2010 Internet Archive. Software license AGPL version 3.
6 * This file is part of BookReader. The full source code can be found at GitHub:
7 * http://github.com/openlibrary/bookreader
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
18 // Operators recognized in BookReader download URLs
19 public static $downloadOperators = array('page');
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)
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}
29 $foundScandata = false;
30 $foundImageStack = false;
32 $targetScandata = $prefix . "_scandata.xml";
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)$@';
39 $baseLength = strlen($item->metadataGrabber->mainDir . '/');
40 foreach ($item->getFiles() as $location => $fileInfo) {
41 $filename = substr($location, $baseLength);
43 if ($checkOldScandata) {
44 if ($filename == 'scandata.xml' || $filename == 'scandata.zip') {
45 $foundScandata = $filename;
49 if ($filename == $targetScandata) {
50 $foundScandata = $filename;
53 if (preg_match($imageFormatRegex, $filename)) {
54 $foundImageStack = $filename;
58 if ($foundScandata && $foundImageStack) {
65 // Finds the prefix to use for the book given the part of the URL trailing after /stream/
66 public static function findPrefix($urlPortion)
68 if (!preg_match('#[^/&?]+#', $urlPortion, $matches)) {
69 // URL portion was empty or started with /, &, or ? -- no item identifier
73 $prefix = $matches[0]; // item identifier
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 --
86 // $$$ would be cleaner to use different templates instead of the uiMode param
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')
92 // Set title to default if not set
94 $title = 'BookReader';
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
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();
111 echo "No identifier specified!";
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';
122 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
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>
131 <meta http-equiv="refresh" content="2; URL=/bookreader/browserunsupported.php?id=<? echo($id); ?>">
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); ?>">
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 -->
147 <script type="text/javascript" src="/includes/excanvas.compiled.js"></script>
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>
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
159 <? } /* mobile user agent */ ?>
161 <body style="background-color: ##939598;">
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>
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>
173 <div id="BookReader">
174 Internet Archive BookReader - <? echo $title; ?>
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>.
184 <script type="text/javascript">
185 // Set some config variables -- $$$ NB: Config object format has not been finalized
187 <? if ($uiMode == 'embed') { ?>
188 brConfig["mode"] = 1;
189 brConfig["ui"] = "embed";
191 brConfig["mode"] = 2;
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> -->
200 <script type="text/javascript">
202 if(window.archive_analytics) { window.archive_analytics.values['bookreader'] = 'open'};
208 // Returns the user part of dev host from URL, or null
209 public static function getDevHost($server)
211 if (preg_match("/^www-(\w+)/", $_SERVER["SERVER_NAME"], $match)) {
219 public static function serverBaseURL($server)
221 // Check if we're on a dev vhost and point to JSIA in the user's public_html
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;
235 public static function jsMetadataURL($server, $identifier, $mainDir, $subPrefix = '')
237 $serverBaseURL = BookReader::serverBaseURL($server);
239 $params = array( 'id' => $identifier, 'itemPath' => $mainDir, 'server' => $server );
241 $params['subPrefix'] = $subPrefix;
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) {
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 = '')
261 $locateURL = 'http://www.archive.org/bookreader/BookReaderJSLocate.php?id=' . $identifier;
263 $locateURL .= '&subPrefix=' . $subPrefix;
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
273 $urlParts = BookReader::parsePath($path);
275 // Check for non-handled cases
276 $required = array('identifier', 'operator', 'operand');
277 foreach ($required as $key) {
278 if (!array_key_exists($key, $urlParts)) {
283 $identifier = $urlParts['identifier'];
284 $operator = $urlParts['operator'];
285 $filename = $urlParts['operand'];
286 $subPrefix = $urlParts['subPrefix'];
288 $serverBaseURL = BookReader::serverBaseURL($item->getServer());
290 // Baseline query params
293 'itemPath' => $item->getMainDir(),
294 'server' => $serverBaseURL
297 $query['subPrefix'] = $subPrefix;
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
307 $query['page'] = $page;
308 return 'http://' . $serverBaseURL . '/BookReader/BookReaderPreview.php?' . http_build_query($query, '', '&');
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, '', '&');
317 // Asking for a non-preview page
318 $query['page'] = $filename;
319 return 'http://' . $serverBaseURL . '/BookReader/BookReaderImages.php?' . http_build_query($query, '', '&');
326 return null; // was not handled
329 public static function browserFromUserAgent($userAgent) {
330 $browserPatterns = array(
332 'iphone' => '/iPhone/', // Also cover iPod Touch
333 'android' => '/Android/',
336 foreach ($browserPatterns as $browser => $pattern) {
337 if (preg_match($pattern, $userAgent)) {
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' ),
354 $browser = BookReader::browserFromUserAgent($userAgent);
356 return $browserParams[$browser];
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')
368 // Pull off query, e.g. ?foo=bar
369 if (preg_match('#(.*?)(\?.*)#', $path, $matches) === 1) {
370 $parts['query'] = $matches[2];
374 // Pull off identifier
375 if (preg_match('#[^/&?]+#', $path, $matches) === 0) {
379 $parts['identifier'] = $matches[0];
380 $path = substr($path, strlen($matches[0]));
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'];
391 $parts['subPrefix'] = $path;