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
19 // Operators recognized in BookReader download URLs
20 public static $downloadOperators = array('page');
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)
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}
30 $foundScandata = false;
31 $foundImageStack = false;
33 $targetScandata = $prefix . "_scandata.xml";
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)$@';
40 $baseLength = strlen($item->getMainDir() . '/');
41 foreach ($item->getFiles() as $location => $fileInfo) {
42 $filename = substr($location, $baseLength);
44 if ($checkOldScandata) {
45 if ($filename == 'scandata.xml' || $filename == 'scandata.zip') {
46 $foundScandata = $filename;
50 if ($filename == $targetScandata) {
51 $foundScandata = $filename;
54 if (preg_match($imageFormatRegex, $filename)) {
55 $foundImageStack = $filename;
59 if ($foundScandata && $foundImageStack) {
66 // Finds the prefix to use for the book given the part of the URL trailing after /stream/
67 public static function findPrefix($urlPortion)
69 if (!preg_match('#[^/&?]+#', $urlPortion, $matches)) {
70 // URL portion was empty or started with /, &, or ? -- no item identifier
74 $prefix = $matches[0]; // item identifier
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 --
87 // $$$ would be cleaner to use different templates instead of the uiMode param
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)
93 // Set title to default if not set
95 $title = 'BookReader';
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
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();
112 echo "No identifier specified!";
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';
124 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
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>
134 <meta http-equiv="refresh" content="2; URL=/bookreader/browserunsupported.php?id=<? echo($id); ?>">
137 <!-- THIS ALLOWS BEAUTYTIPS TO WORK ON IE -->
139 <script type="text/javascript" src="/includes/excanvas.compiled.js"></script>
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); ?>">
149 <? if ($protected) { ?>
150 <link rel="stylesheet" type="text/css" href="/bookreader/BookReaderLending.css?v=<? echo($version); ?>">
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>
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>
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
169 <? } /* mobile user agent */ ?>
171 <body style="background-color: ##939598;">
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;
193 <? if ($isAdmin == true) {
194 echo ' brConfig["isAdmin"] = true;';
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); ?>
204 <script type="text/javascript" src="<? echo($metaURL); ?>"></script>
206 <script type="text/javascript">
208 if(window.archive_analytics) { window.archive_analytics.values['bookreader'] = 'open'};
213 <? BookReader::emitForIE7($server, $mainDir, $identifier, $subPrefix, $title, $coverLeaf, $titleStart, $uiMode); ?>
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')
230 // Set title to default if not set
232 $title = 'BookReader';
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
242 if (BookReader::getDevHost($server)) {
243 // on dev host - add time to force reload
244 $version .= '_' . time();
248 echo "No identifier specified!";
252 $metaURL = BookReader::jsMetadataURLForIE7($server, $identifier, $mainDir, $subPrefix);
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); ?>">
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>
268 <body style="background-color: #FFFFFF;">
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>
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>
276 <script type="text/javascript">
277 // Set some config variables - $$$ NB: Config object format has not been finalized
279 <? if ($uiMode == 'embed') { ?>
280 brConfig["mode"] = 1;
281 brConfig["reduce"] = 8;
282 brConfig["ui"] = "embed";
284 brConfig["mode"] = 2;
287 <script type="text/javascript" src="<? echo($metaURL); ?>"></script>
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" />
296 <div id="BookReaderSearchResults">
303 <div class="BRlogotype">
304 <a href="http://archive.org/" class="BRblack">Internet Archive</a>
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>
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');
322 if(window.archive_analytics) { window.archive_analytics.values['bookreader'] = 'open'};
328 // Returns the user part of dev host from URL, or null
329 public static function getDevHost($server)
331 if (preg_match("/^www-(\w+)/", $_SERVER["SERVER_NAME"], $match)) {
339 public static function serverBaseURL($server)
341 // Check if we're on a dev vhost and point to JSIA in the user's public_html
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;
355 public static function jsMetadataURL($server, $identifier, $mainDir, $subPrefix = '')
357 $serverBaseURL = BookReader::serverBaseURL($server);
359 $params = array( 'id' => $identifier, 'itemPath' => $mainDir, 'server' => $server );
361 $params['subPrefix'] = $subPrefix;
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) {
377 public static function jsMetadataURLForIE7($server, $identifier, $mainDir, $subPrefix = '')
379 $serverBaseURL = BookReader::serverBaseURL($server);
381 $params = array( 'id' => $identifier, 'itemPath' => $mainDir, 'server' => $server );
383 $params['subPrefix'] = $subPrefix;
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) {
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 = '')
404 $locateURL = 'http://www.archive.org/bookreader/BookReaderJSLocate.php?id=' . $identifier;
406 $locateURL .= '&subPrefix=' . $subPrefix;
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
416 $urlParts = BookReader::parsePath($path);
418 // Check for non-handled cases
419 $required = array('identifier', 'operator', 'operand');
420 foreach ($required as $key) {
421 if (!array_key_exists($key, $urlParts)) {
426 $identifier = $urlParts['identifier'];
427 $operator = $urlParts['operator'];
428 $filename = $urlParts['operand'];
429 $subPrefix = $urlParts['subPrefix'];
431 $serverBaseURL = BookReader::serverBaseURL($item->getServer());
433 // Baseline query params
436 'itemPath' => $item->getMainDir(),
437 'server' => $serverBaseURL
440 $query['subPrefix'] = $subPrefix;
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
450 $query['page'] = $page;
451 return 'http://' . $serverBaseURL . '/BookReader/BookReaderPreview.php?' . http_build_query($query, '', '&');
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, '', '&');
460 // Asking for a non-preview page
461 $query['page'] = $filename;
462 return 'http://' . $serverBaseURL . '/BookReader/BookReaderImages.php?' . http_build_query($query, '', '&');
469 return null; // was not handled
472 public static function browserFromUserAgent($userAgent) {
473 $browserPatterns = array(
475 'iphone' => '/iPhone/', // Also covers iPod Touch
476 'android' => '/Android/',
479 foreach ($browserPatterns as $browser => $pattern) {
480 if (preg_match($pattern, $userAgent)) {
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( ), // Presence of this OS doesn't tell us much about device
497 $browser = BookReader::browserFromUserAgent($userAgent);
499 return $browserParams[$browser];
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')
511 // Pull off query, e.g. ?foo=bar
512 if (preg_match('#(.*?)(\?.*)#', $path, $matches) === 1) {
513 $parts['query'] = $matches[2];
517 // Pull off identifier
518 if (preg_match('#[^/&?]+#', $path, $matches) === 0) {
522 $parts['identifier'] = $matches[0];
523 $path = substr($path, strlen($matches[0]));
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'];
534 $parts['subPrefix'] = $path;