3 Copyright(c)2008 Internet Archive. Software license AGPL version 3.
5 This file is part of BookReader.
7 BookReader is free software: you can redistribute it and/or modify
8 it under the terms of the GNU Affero General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 BookReader is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Affero General Public License for more details.
17 You should have received a copy of the GNU Affero General Public License
18 along with BookReader. If not, see <http://www.gnu.org/licenses/>.
21 header('Content-Type: application/javascript');
23 $id = $_REQUEST['id'];
24 $itemPath = $_REQUEST['itemPath'];
25 $subPrefix = $_REQUEST['subPrefix'];
26 $server = $_REQUEST['server'];
28 // $$$mang this code has been refactored into BookReaderMeta.inc.php for use e.g. by
29 // BookReaderPreview.php and BookReaderImages.php. The code below should be
30 // taken out and replaced by calls into BookReaderMeta
32 // Check if we're on a dev vhost and point to JSIA in the user's public_html on the datanode
34 // $$$ TODO consolidate this logic
35 if (strpos($_SERVER["REQUEST_URI"], "/~mang") === 0) { // Serving out of home dir
36 $server .= ':80/~mang';
37 } else if (strpos($_SERVER["REQUEST_URI"], "/~rkumar") === 0) { // Serving out of home dir
38 $server .= ':80/~rkumar';
39 } else if (strpos($_SERVER["REQUEST_URI"], "/~testflip") === 0) { // Serving out of home dir
40 $server .= ':80/~testflip';
46 $subItemPath = $itemPath . '/' . $subPrefix;
49 BRFatal("No identifier specified!");
52 if ("" == $itemPath) {
53 BRFatal("No itemPath specified!");
57 BRFatal("No server specified!");
60 if (!preg_match("|^/\d+/items/{$id}$|", $itemPath)) {
64 // XXX check here that subitem is okay
66 $filesDataFile = "$itemPath/${id}_files.xml";
68 if (file_exists($filesDataFile)) {
69 $filesData = simplexml_load_file("$itemPath/${id}_files.xml");
71 BRfatal("File metadata not found!");
74 $imageStackInfo = findImageStack($subPrefix, $filesData);
75 if ($imageStackInfo['imageFormat'] == 'unknown') {
76 BRfatal('Couldn\'t find image stack');
79 $imageFormat = $imageStackInfo['imageFormat'];
80 $archiveFormat = $imageStackInfo['archiveFormat'];
81 $imageStackFile = $itemPath . "/" . $imageStackInfo['imageStackFile'];
83 if ("unknown" == $imageFormat) {
84 BRfatal("Unknown image format");
87 if ("unknown" == $archiveFormat) {
88 BRfatal("Unknown archive format");
92 $scanDataFile = "${subItemPath}_scandata.xml";
93 $scanDataZip = "$itemPath/scandata.zip";
94 if (file_exists($scanDataFile)) {
95 $scanData = simplexml_load_file($scanDataFile);
96 } else if (file_exists($scanDataZip)) {
97 $cmd = 'unzip -p ' . escapeshellarg($scanDataZip) . ' scandata.xml';
98 exec($cmd, $output, $retval);
99 if ($retval != 0) BRFatal("Could not unzip ScanData!");
101 $dump = join("\n", $output);
102 $scanData = simplexml_load_string($dump);
103 } else if (file_exists("$itemPath/scandata.xml")) {
104 // For e.g. Scribe v.0 books!
105 $scanData = simplexml_load_file("$itemPath/scandata.xml");
107 BRFatal("ScanData file not found!");
110 $metaDataFile = "$itemPath/{$id}_meta.xml";
111 if (!file_exists($metaDataFile)) {
112 BRFatal("MetaData file not found!");
116 $metaData = simplexml_load_file($metaDataFile);
118 //$firstLeaf = $scanData->pageData->page[0]['leafNum'];
121 // Error reporting - this helps us fix errors quickly
122 function logError(description,page,line) {
123 if (typeof(archive_analytics) != 'undefined') {
125 'bookreader': 'error',
126 'description': description,
129 'itemid': '<?echo $id;?>',
130 'subPrefix': '<?echo $subPrefix;?>',
131 'server': '<?echo $server;?>',
132 'bookPath': '<?echo $subItemPath;?>'
135 // if no referrer set '-' as referrer
136 if (document.referrer == '') {
137 values['referrer'] = '-';
139 values['referrer'] = document.referrer;
142 if (typeof(br) != 'undefined') {
143 values['itemid'] = br.bookId;
144 values['subPrefix'] = br.subPrefix;
145 values['server'] = br.server;
146 values['bookPath'] = br.bookPath;
149 var qs = archive_analytics.format_bug(values);
151 var error_img = new Image(100,25);
152 error_img.src = archive_analytics.img_src + "?" + qs;
155 return false; // allow browser error handling so user sees there was a problem
157 window.onerror=logError;
159 br = new BookReader();
162 /* Output title leaf if marked */
164 foreach ($scanData->pageData->page as $page) {
165 if (("Title Page" == $page->pageType) || ("Title" == $page->pageType)) {
166 $titleLeaf = "{$page['leafNum']}";
171 if ('' != $titleLeaf) {
172 printf("br.titleLeaf = %d;\n", $titleLeaf);
176 br.getPageWidth = function(index) {
177 return this.pageW[index];
180 br.getPageHeight = function(index) {
181 return this.pageH[index];
184 // Returns true if page image is available rotated
185 br.canRotatePage = function(index) {
186 return 'jp2' == this.imageFormat; // Assume single format for now
189 // reduce defaults to 1 (no reduction)
190 // rotate defaults to 0 (no rotation)
191 br.getPageURI = function(index, reduce, rotate) {
195 if ('undefined' == typeof(reduce)) {
200 if ('undefined' == typeof(rotate)) {
206 var file = this._getPageFile(index);
208 // $$$ add more image stack formats here
209 return 'http://'+this.server+'/BookReader/BookReaderImages.php?zip='+this.zip+'&file='+file+'&scale='+_reduce+'&rotate='+_rotate;
212 // Get a rectangular region out of a page
213 br.getRegionURI = function(index, reduce, rotate, sourceX, sourceY, sourceWidth, sourceHeight) {
215 // Map function arguments to the url keys
216 var urlKeys = ['n', 'r', 'rot', 'x', 'y', 'w', 'h'];
218 for (var i = 0; i < arguments.length; i++) {
219 if ('undefined' != typeof(arguments[i])) {
223 page += urlKeys[i] + arguments[i];
227 var itemPath = this.bookPath.replace(new RegExp('/'+this.subPrefix+'$'), ''); // remove trailing subPrefix
229 return 'http://'+this.server+'/BookReader/BookReaderImages.php?id=' + this.bookId + '&itemPath=' + itemPath + '&server=' + this.server + '&subPrefix=' + this.subPrefix + '&page=' +page + '.jpg';
232 br._getPageFile = function(index) {
233 var leafStr = '0000';
234 var imgStr = this.leafMap[index].toString();
235 var re = new RegExp("0{"+imgStr.length+"}$");
237 var insideZipPrefix = this.subPrefix.match('[^/]+$');
238 var file = insideZipPrefix + '_' + this.imageFormat + '/' + insideZipPrefix + '_' + leafStr.replace(re, imgStr) + '.' + this.imageFormat;
243 br.getPageSide = function(index) {
244 //assume the book starts with a cover (right-hand leaf)
245 //we should really get handside from scandata.xml
247 <? // Use special function if we should infer the page sides based off the title page index
248 if (preg_match('/goog$/', $id) && ('' != $titleLeaf)) {
250 // assume page side based on title pagex
251 var titleIndex = br.leafNumToIndex(br.titleLeaf);
252 // assume title page is RHS
253 var delta = titleIndex - index;
254 if (0 == (delta & 0x1)) {
264 // $$$ we should get this from scandata instead of assuming the accessible
265 // leafs are contiguous
266 if ('rl' != this.pageProgression) {
267 // If pageProgression is not set RTL we assume it is LTR
268 if (0 == (index & 0x1)) {
269 // Even-numbered page
277 if (0 == (index & 0x1)) {
285 br.getPageNum = function(index) {
286 var pageNum = this.pageNums[index];
294 // Single images in the Internet Archive scandata.xml metadata are (somewhat incorrectly)
295 // given a "leaf" number. Some of these images from the scanning process should not
296 // be displayed in the BookReader (for example colour calibration cards). Since some
297 // of the scanned images will not be displayed in the BookReader (those marked with
298 // addToAccessFormats false in the scandata.xml) leaf numbers and BookReader page
299 // indexes are generally not the same. This function returns the BookReader page
300 // index given a scanned leaf number.
302 // This function is used, for example, to map between search results (that use the
303 // leaf numbers) and the displayed pages in the BookReader.
304 br.leafNumToIndex = function(leafNum) {
305 for (var index = 0; index < this.leafMap.length; index++) {
306 if (this.leafMap[index] == leafNum) {
314 // This function returns the left and right indices for the user-visible
315 // spread that contains the given index. The return values may be
316 // null if there is no facing page or the index is invalid.
317 br.getSpreadIndices = function(pindex) {
318 // $$$ we could make a separate function for the RTL case and
319 // only bind it if necessary instead of always checking
320 // $$$ we currently assume there are no gaps
322 var spreadIndices = [null, null];
323 if ('rl' == this.pageProgression) {
325 if (this.getPageSide(pindex) == 'R') {
326 spreadIndices[1] = pindex;
327 spreadIndices[0] = pindex + 1;
329 // Given index was LHS
330 spreadIndices[0] = pindex;
331 spreadIndices[1] = pindex - 1;
335 if (this.getPageSide(pindex) == 'L') {
336 spreadIndices[0] = pindex;
337 spreadIndices[1] = pindex + 1;
339 // Given index was RHS
340 spreadIndices[1] = pindex;
341 spreadIndices[0] = pindex - 1;
345 //console.log(" index %d mapped to spread %d,%d", pindex, spreadIndices[0], spreadIndices[1]);
347 return spreadIndices;
350 // Remove the page number assertions for all but the highest index page with
351 // a given assertion. Ensures there is only a single page "{pagenum}"
352 // e.g. the last page asserted as page 5 retains that assertion.
353 br.uniquifyPageNums = function() {
356 for (var i = br.pageNums.length - 1; i--; i >= 0) {
357 var pageNum = br.pageNums[i];
358 if ( !seen[pageNum] ) {
359 seen[pageNum] = true;
361 br.pageNums[i] = null;
367 br.cleanupMetadata = function() {
368 br.uniquifyPageNums();
373 // Returns a URL for an embedded version of the current book
374 br.getEmbedURL = function(viewParams) {
375 // We could generate a URL hash fragment here but for now we just leave at defaults
376 var url = 'http://' + window.location.host + '/stream/'+this.bookId;
377 if (this.subPrefix != this.bookId) { // Only include if needed
378 url += '/' + this.subPrefix;
381 if (typeof(viewParams) != 'undefined') {
382 url += '#' + this.fragmentFromParams(viewParams);
389 // Returns the embed code HTML fragment suitable for copy and paste
390 br.getEmbedCode = function(frameWidth, frameHeight, viewParams) {
391 return "<iframe src='" + this.getEmbedURL(viewParams) + "' width='" + frameWidth + "' height='" + frameHeight + "' frameborder='0' ></iframe>";
394 // getOpenLibraryRecord
395 br.getOpenLibraryRecord = function(callback) {
396 // Try looking up by ocaid first, then by source_record
398 var self = this; // closure
400 var jsonURL = self.olHost + '/query.json?type=/type/edition&*=&ocaid=' + self.bookId;
403 success: function(data) {
404 if (data && data.length > 0) {
405 callback(self, data[0]);
408 jsonURL = self.olHost + '/query.json?type=/type/edition&*=&source_records=ia:' + self.bookId;
411 success: function(data) {
412 if (data && data.length > 0) {
413 callback(self, data[0]);
424 br.buildInfoDiv = function(jInfoDiv) {
425 // $$$ it might make more sense to have a URL on openlibrary.org that returns this info
427 var escapedTitle = BookReader.util.escapeHTML(this.bookTitle);
428 var domainRe = /(\w+\.(com|org))/;
429 var domainMatch = domainRe.exec(this.bookUrl);
430 var domain = this.bookUrl;
432 domain = domainMatch[1];
435 // $$$ cover looks weird before it loads
436 jInfoDiv.find('.BRfloatCover').append([
437 '<div style="height: 140px; min-width: 80px; padding: 0; margin: 0;"><a href="', this.bookUrl, '"><img src="http://www.archive.org/download/', this.bookId, '/page/cover_t.jpg" alt="' + escapedTitle + '" height="140px" /></a></div>'].join('')
440 jInfoDiv.find('.BRfloatMeta').append([
442 //'<p>Published ', this.bookPublished,
443 //, <a href="Open Library Publisher Page">Publisher name</a>',
445 //'<p>Written in <a href="Open Library Language page">Language</a></p>',
446 '<h3>Other Formats</h3>',
447 '<ul class="links">',
448 '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '.pdf">PDF</a><span>|</span></li>',
449 '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '_djvu.txt">Plain Text</a><span>|</span></li>',
450 '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '_daisy.zip">DAISY</a><span>|</span></li>',
451 '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '.epub">ePub</a><span>|</span></li>',
452 '<li><a href="https://www.amazon.com/gp/digital/fiona/web-to-kindle?clientid=IA&itemid=', this.bookId, '&docid=', this.subPrefix, '">Send to Kindle</a></li>',
454 '<p class="moreInfo"><span></span>More information on <a href="'+ this.bookUrl + '">' + domain + '</a> </p>'].join('\n'));
456 jInfoDiv.find('.BRfloatFoot').append([
458 '<a href="http://openlibrary.org/contact" class="problem">Report a problem</a>',
461 if (domain == 'archive.org') {
462 jInfoDiv.find('.BRfloatMeta p.moreInfo span').css(
463 {'background': 'url(http://www.archive.org/favicon.ico) no-repeat', 'width': 22, 'height': 18 }
467 jInfoDiv.find('.BRfloatTitle a').attr({'href': this.bookUrl, 'alt': this.bookTitle}).text(this.bookTitle);
468 var bookPath = (window.location + '').replace('#','%23');
469 jInfoDiv.find('a.problem').attr('href','http://openlibrary.org/contact?path=' + bookPath);
476 foreach ($scanData->pageData->page as $page) {
477 if (shouldAddPage($page)) {
478 if(0 != $i) echo ","; //stupid IE
479 echo "{$page->cropBox->w}";
490 foreach ($scanData->pageData->page as $page) {
491 if (shouldAddPage($page)) {
492 if(0 != $i) echo ","; //stupid IE
493 echo "{$page->cropBox->h}";
494 $totalHeight += intval($page->cropBox->h/4) + 10;
503 foreach ($scanData->pageData->page as $page) {
504 if (shouldAddPage($page)) {
505 if(0 != $i) echo ","; //stupid IE
506 echo "{$page['leafNum']}";
516 foreach ($scanData->pageData->page as $page) {
517 if (shouldAddPage($page)) {
518 if(0 != $i) echo ","; //stupid IE
519 if (array_key_exists('pageNumber', $page) && ('' != $page->pageNumber)) {
520 echo "'{$page->pageNumber}'";
531 br.numLeafs = br.pageW.length;
533 br.bookId = '<?echo $id;?>';
534 br.zip = '<?echo $imageStackFile;?>';
535 br.subPrefix = '<?echo $subPrefix;?>';
536 br.server = '<?echo $server;?>';
537 br.bookTitle= '<?echo preg_replace("/\'/", "\\'", $metaData->title);?>';
538 br.bookPath = '<?echo $subItemPath;?>';
539 br.bookUrl = '<?echo "http://www.archive.org/details/$id";?>';
540 br.imageFormat = '<?echo $imageFormat;?>';
541 br.archiveFormat = '<?echo $archiveFormat;?>';
545 # Load some values from meta.xml
546 if ('' != $metaData->{'page-progression'}) {
547 echo "br.pageProgression = '" . $metaData->{"page-progression"} . "';\n";
549 // Assume page progression is Left To Right
550 echo "br.pageProgression = 'lr';\n";
555 foreach ($metaData->xpath('//collection') as $collection) {
556 if('browserlending' == $collection) {
562 echo "br.olHost = 'http://openlibrary.org';\n";
563 #echo "br.olHost = 'http://mang-dev.us.archive.org:8080';\n";
566 echo "br.olAuth = true;\n";
568 echo "br.olAuth = false;\n";
572 echo "br.protected = true;\n";
575 # Default options for BookReader
576 if ('' != $metaData->{'bookreader-defaults'}) {
577 echo "br.defaults = '" . $metaData->{'bookreader-defaults'} . "';\n";
582 // Check for config object
583 // $$$ change this to use the newer params object
584 if (typeof(brConfig) != 'undefined') {
585 if (typeof(brConfig["ui"]) != 'undefined') {
586 br.ui = brConfig["ui"];
589 if (brConfig['mode'] == 1) {
591 if (typeof(brConfig['reduce'] != 'undefined')) {
592 br.reduce = brConfig['reduce'];
594 } else if (brConfig['mode'] == 2) {
598 if (typeof(brConfig["isAdmin"]) != 'undefined') {
599 br.isAdmin = brConfig["isAdmin"];
607 this.olConnect = false;
608 this.loanUUID = false;
609 this.permsToken = false;
611 var cookieRe = /;\s*/;
612 var cookies = document.cookie.split(cookieRe);
613 var length = cookies.length;
615 for (i=0; i<length; i++) {
616 if (0 == cookies[i].indexOf('br-loan-' + br.bookId)) {
617 this.loanUUID = cookies[i].split('=')[1];
619 if (0 == cookies[i].indexOf('loan-' + br.bookId)) {
620 this.permsToken = cookies[i].split('=')[1];
623 // Set olHost to use if passed in
624 if (0 == cookies[i].indexOf('ol-host')) {
625 br.olHost = 'http://' + unescape(cookies[i].split('=')[1]);
629 this.authUrl = br.olHost + '/ia_auth/' + br.bookId;
634 OLAuth.prototype.init = function() {
635 var htmlStr = 'Checking loan status with Open Library';
637 this.showPopup("#F0EEE2", "#000", htmlStr, 'Please wait as we check the status of this book...');
638 var authUrl = this.authUrl+'?rand='+Math.random();
639 if (false !== this.loanUUID) {
640 authUrl += '&loan='+this.loanUUID
642 if (false !== this.permsToken) {
643 authUrl += '&token='+this.permsToken
645 $.ajax({url:authUrl, dataType:'jsonp', jsonpCallback:'olAuth.initCallback'});
648 OLAuth.prototype.showPopup = function(bgColor, textColor, msg, resolution) {
649 this.popup = document.createElement("div");
651 position: 'absolute',
653 left: ($('#BookReader').attr('clientWidth')-400)/2 + 'px',
656 border: "3px double #999999",
659 backgroundColor: bgColor,
661 }).appendTo('#BookReader');
663 this.setPopupMsg(msg, resolution);
667 OLAuth.prototype.setPopupMsg = function(msg, resolution) {
668 this.popup.innerHTML = ['<p><strong>', msg, '</strong></p><p>', resolution, '</p>'].join('\n');
671 OLAuth.prototype.showError = function(msg, resolution) {
673 backgroundColor: "#fff",
677 this.setPopupMsg(msg, resolution);
680 OLAuth.prototype.initCallback = function(obj) {
681 if (false == obj.success) {
683 ret = confirm("We couldn't authenticate your loan with Open Library, but since you are an administrator or uploader of this book, you can access this book for QA purposes. Would you like to QA this book?");
685 this.showError(obj.msg, obj.resolution)
690 this.showError(obj.msg, obj.resolution)
693 //user is authenticated
694 this.setCookie(obj.token);
695 this.olConnect = true;
701 OLAuth.prototype.callback = function(obj) {
702 if (false == obj.success) {
703 this.showPopup("#F0EEE2", "#000", obj.msg, obj.resolution);
704 clearInterval(this.poller);
705 this.ttsPoller = null;
707 this.olConnect = true;
708 this.setCookie(obj.token);
712 OLAuth.prototype.setCookie = function(value) {
713 var date = new Date();
714 date.setTime(date.getTime()+(10*60*1000)); //10 min expiry
715 var expiry = date.toGMTString();
716 var cookie = 'loan-'+br.bookId+'='+value;
717 cookie += '; expires='+expiry;
718 cookie += '; path=/; domain=.archive.org;';
719 document.cookie = cookie;
720 this.permsToken = value;
722 //refresh the br-loan uuid cookie with current expiry, if needed
723 if (false !== this.loanUUID) {
724 cookie = 'br-loan-'+br.bookId+'='+this.loanUUID;
725 cookie += '; expires='+expiry;
726 cookie += '; path=/; domain=.archive.org;';
727 document.cookie = cookie;
731 OLAuth.prototype.deleteCookies = function() {
732 var date = new Date();
733 date.setTime(date.getTime()-(24*60*60*1000)); //one day ago
734 var expiry = date.toGMTString();
735 var cookie = 'loan-'+br.bookId+'=""';
736 cookie += '; expires='+expiry;
737 cookie += '; path=/; domain=.archive.org;';
738 document.cookie = cookie;
740 cookie = 'br-loan-'+br.bookId+'=""';
741 cookie += '; expires='+expiry;
742 cookie += '; path=/; domain=.archive.org;';
743 document.cookie = cookie;
746 OLAuth.prototype.startPolling = function () {
748 this.poller=setInterval(function(){
749 if (!self.olConnect) {
750 self.showPopup("#F0EEE2", "#000", 'Connection error', 'The BookReader cannot reach Open Library. This might mean that you are offline or that Open Library is down. Please check your Internet connection and refresh this page or try again later.');
751 clearInterval(self.poller);
752 self.ttsPoller = null;
754 self.olConnect = false;
755 //be sure to add random param to authUrl to avoid stale cache
756 var authUrl = self.authUrl+'?rand='+Math.random();
757 if (false !== self.loanUUID) {
758 authUrl += '&loan='+self.loanUUID
760 if (false !== self.permsToken) {
761 authUrl += '&token='+self.permsToken
764 $.ajax({url:authUrl, dataType:'jsonp', jsonpCallback:'olAuth.callback'});
766 },300000); //five minute interval
769 br.cleanupMetadata();
771 var olAuth = new OLAuth();
779 function BRFatal($string) {
783 if (typeof(archive_analytics) != 'undefined') {
785 'bookreader': 'fatal',
786 'description': "<? echo $string; ?>",
787 'itemid': "<? echo $_REQUEST['id']; ?>",
788 'server': "<? echo $_REQUEST['server']; ?>",
789 'request_uri': "<? echo $_SERVER["REQUEST_URI"]; ?>"
792 if (document.referrer == '') {
793 values['referrer'] = '-';
795 values['referrer'] = document.referrer;
798 var qs = archive_analytics.format_bug(values);
800 var error_img = new Image(100,25);
801 error_img.src = archive_analytics.img_src + "?" + qs;
804 alert("<? echo $string;?>");
811 // Returns true if a page should be added based on it's information in
813 function shouldAddPage($page) {
814 // Return false only if the page is marked addToAccessFormats false.
815 // If there is no assertion we assume it should be added.
816 if (isset($page->addToAccessFormats)) {
817 if ("false" == strtolower(trim($page->addToAccessFormats))) {
825 // Returns { 'imageFormat' => , 'archiveFormat' => '} given a sub-item prefix and loaded xml data
826 function findImageStack($subPrefix, $filesData) {
828 // $$$ The order of the image formats determines which will be returned first
829 $imageFormats = array('JP2' => 'jp2', 'TIFF' => 'tif', 'JPEG' => 'jpg');
830 $archiveFormats = array('ZIP' => 'zip', 'Tar' => 'tar');
831 $imageGroup = implode('|', array_keys($imageFormats));
832 $archiveGroup = implode('|', array_keys($archiveFormats));
833 // $$$ Currently only return processed images
834 $imageStackRegex = "/Single Page (Processed) (${imageGroup}) (${archiveGroup})/";
836 foreach ($filesData->file as $file) {
837 if (strpos($file['name'], $subPrefix) === 0) { // subprefix matches beginning
838 if (preg_match($imageStackRegex, $file->format, $matches)) {
840 // Make sure we have a regular image stack
841 $imageFormat = $imageFormats[$matches[2]];
842 if (strpos($file['name'], $subPrefix . '_' . $imageFormat) === 0) {
843 return array('imageFormat' => $imageFormat,
844 'archiveFormat' => $archiveFormats[$matches[3]],
845 'imageStackFile' => $file['name']);
851 return array('imageFormat' => 'unknown', 'archiveFormat' => 'unknown', 'imageStackFile' => 'unknown');