842aa7f9ee5b58268c3fb519b8a53c7fb147f0f5
[bookreader.git] / BookReaderIA / datanode / BookReaderJSIA.php
1 <?
2 /*
3 Copyright(c)2008 Internet Archive. Software license AGPL version 3.
4
5 This file is part of BookReader.
6
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.
11
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.
16
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/>.
19 */
20
21 header('Content-Type: application/javascript');
22
23 $id = $_REQUEST['id'];
24 $itemPath = $_REQUEST['itemPath'];
25 $subPrefix = $_REQUEST['subPrefix'];
26 $server = $_REQUEST['server'];
27
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
31
32 // Check if we're on a dev vhost and point to JSIA in the user's public_html on the datanode
33
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';
41 }
42
43 if (! $subPrefix) {
44     $subPrefix = $id;
45 }
46 $subItemPath = $itemPath . '/' . $subPrefix;
47
48 if ("" == $id) {
49     BRFatal("No identifier specified!");
50 }
51
52 if ("" == $itemPath) {
53     BRFatal("No itemPath specified!");
54 }
55
56 if ("" == $server) {
57     BRFatal("No server specified!");
58 }
59
60 if (!preg_match("|^/\d+/items/{$id}$|", $itemPath)) {
61     BRFatal("Bad id!");
62 }
63
64 // XXX check here that subitem is okay
65
66 $filesDataFile = "$itemPath/${id}_files.xml";
67
68 if (file_exists($filesDataFile)) {
69     $filesData = simplexml_load_file("$itemPath/${id}_files.xml");
70 } else {
71     BRfatal("File metadata not found!");
72 }
73
74 $imageStackInfo = findImageStack($subPrefix, $filesData);
75 if ($imageStackInfo['imageFormat'] == 'unknown') {
76     BRfatal('Couldn\'t find image stack');
77 }
78
79 $imageFormat = $imageStackInfo['imageFormat'];
80 $archiveFormat = $imageStackInfo['archiveFormat'];
81 $imageStackFile = $itemPath . "/" . $imageStackInfo['imageStackFile'];
82
83 if ("unknown" == $imageFormat) {
84   BRfatal("Unknown image format");
85 }
86
87 if ("unknown" == $archiveFormat) {
88   BRfatal("Unknown archive format");
89 }
90
91
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!");
100     
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");
106 } else {
107     BRFatal("ScanData file not found!");
108 }
109
110 $metaDataFile = "$itemPath/{$id}_meta.xml";
111 if (!file_exists($metaDataFile)) {
112     BRFatal("MetaData file not found!");
113 }
114
115
116 $metaData = simplexml_load_file($metaDataFile);
117
118 //$firstLeaf = $scanData->pageData->page[0]['leafNum'];
119 ?>
120
121 // Error reporting - this helps us fix errors quickly
122 function logError(description,page,line) {
123     if (typeof(archive_analytics) != 'undefined') {
124         var values = {
125             'bookreader': 'error',
126             'description': description,
127             'page': page,
128             'line': line,
129             'itemid': '<?echo $id;?>',
130             'subPrefix': '<?echo $subPrefix;?>',
131             'server': '<?echo $server;?>',
132             'bookPath': '<?echo $subItemPath;?>'
133         };
134
135         // if no referrer set '-' as referrer
136         if (document.referrer == '') {
137             values['referrer'] = '-';
138         } else {
139             values['referrer'] = document.referrer;
140         }
141         
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;
147         }
148         
149         var qs = archive_analytics.format_bug(values);
150
151         var error_img = new Image(100,25);
152         error_img.src = archive_analytics.img_src + "?" + qs;
153     }
154
155     return false; // allow browser error handling so user sees there was a problem
156 }
157 window.onerror=logError;
158
159 br = new BookReader();
160
161 <?
162 /* Output title leaf if marked */
163 $titleLeaf = '';
164 foreach ($scanData->pageData->page as $page) {
165     if (("Title Page" == $page->pageType) || ("Title" == $page->pageType)) {
166         $titleLeaf = "{$page['leafNum']}";
167         break;
168     }
169 }
170     
171 if ('' != $titleLeaf) {
172     printf("br.titleLeaf = %d;\n", $titleLeaf);
173 }
174 ?>
175
176 br.getPageWidth = function(index) {
177     return this.pageW[index];
178 }
179
180 br.getPageHeight = function(index) {
181     return this.pageH[index];
182 }
183
184 // Returns true if page image is available rotated
185 br.canRotatePage = function(index) {
186     return 'jp2' == this.imageFormat; // Assume single format for now
187 }
188
189 // reduce defaults to 1 (no reduction)
190 // rotate defaults to 0 (no rotation)
191 br.getPageURI = function(index, reduce, rotate) {
192     var _reduce;
193     var _rotate;
194
195     if ('undefined' == typeof(reduce)) {
196         _reduce = 1;
197     } else {
198         _reduce = reduce;
199     }
200     if ('undefined' == typeof(rotate)) {
201         _rotate = 0;
202     } else {
203         _rotate = rotate;
204     }
205     
206     var file = this._getPageFile(index);
207         
208     // $$$ add more image stack formats here
209     return 'http://'+this.server+'/BookReader/BookReaderImages.php?zip='+this.zip+'&file='+file+'&scale='+_reduce+'&rotate='+_rotate;
210 }
211
212 br._getPageFile = function(index) {
213     var leafStr = '0000';
214     var imgStr = this.leafMap[index].toString();
215     var re = new RegExp("0{"+imgStr.length+"}$");
216     
217     var insideZipPrefix = this.subPrefix.match('[^/]+$');
218     var file = insideZipPrefix + '_' + this.imageFormat + '/' + insideZipPrefix + '_' + leafStr.replace(re, imgStr) + '.' + this.imageFormat;
219     
220     return file;
221 }
222
223 br.getPageSide = function(index) {
224     //assume the book starts with a cover (right-hand leaf)
225     //we should really get handside from scandata.xml
226     
227     <? // Use special function if we should infer the page sides based off the title page index
228     if (preg_match('/goog$/', $id) && ('' != $titleLeaf)) {
229     ?>
230     // assume page side based on title pagex
231     var titleIndex = br.leafNumToIndex(br.titleLeaf);
232     // assume title page is RHS
233     var delta = titleIndex - index;
234     if (0 == (delta & 0x1)) {
235         // even delta
236         return 'R';
237     } else {
238         return 'L';
239     }
240     <?
241     }
242     ?>
243     
244     // $$$ we should get this from scandata instead of assuming the accessible
245     //     leafs are contiguous
246     if ('rl' != this.pageProgression) {
247         // If pageProgression is not set RTL we assume it is LTR
248         if (0 == (index & 0x1)) {
249             // Even-numbered page
250             return 'R';
251         } else {
252             // Odd-numbered page
253             return 'L';
254         }
255     } else {
256         // RTL
257         if (0 == (index & 0x1)) {
258             return 'L';
259         } else {
260             return 'R';
261         }
262     }
263 }
264
265 br.getPageNum = function(index) {
266     var pageNum = this.pageNums[index];
267     if (pageNum) {
268         return pageNum;
269     } else {
270         return 'n' + index;
271     }
272 }
273
274 // Single images in the Internet Archive scandata.xml metadata are (somewhat incorrectly)
275 // given a "leaf" number.  Some of these images from the scanning process should not
276 // be displayed in the BookReader (for example colour calibration cards).  Since some
277 // of the scanned images will not be displayed in the BookReader (those marked with
278 // addToAccessFormats false in the scandata.xml) leaf numbers and BookReader page
279 // indexes are generally not the same.  This function returns the BookReader page
280 // index given a scanned leaf number.
281 //
282 // This function is used, for example, to map between search results (that use the
283 // leaf numbers) and the displayed pages in the BookReader.
284 br.leafNumToIndex = function(leafNum) {
285     for (var index = 0; index < this.leafMap.length; index++) {
286         if (this.leafMap[index] == leafNum) {
287             return index;
288         }
289     }
290     
291     return null;
292 }
293
294 // This function returns the left and right indices for the user-visible
295 // spread that contains the given index.  The return values may be
296 // null if there is no facing page or the index is invalid.
297 br.getSpreadIndices = function(pindex) {
298     // $$$ we could make a separate function for the RTL case and
299     //      only bind it if necessary instead of always checking
300     // $$$ we currently assume there are no gaps
301     
302     var spreadIndices = [null, null]; 
303     if ('rl' == this.pageProgression) {
304         // Right to Left
305         if (this.getPageSide(pindex) == 'R') {
306             spreadIndices[1] = pindex;
307             spreadIndices[0] = pindex + 1;
308         } else {
309             // Given index was LHS
310             spreadIndices[0] = pindex;
311             spreadIndices[1] = pindex - 1;
312         }
313     } else {
314         // Left to right
315         if (this.getPageSide(pindex) == 'L') {
316             spreadIndices[0] = pindex;
317             spreadIndices[1] = pindex + 1;
318         } else {
319             // Given index was RHS
320             spreadIndices[1] = pindex;
321             spreadIndices[0] = pindex - 1;
322         }
323     }
324     
325     //console.log("   index %d mapped to spread %d,%d", pindex, spreadIndices[0], spreadIndices[1]);
326     
327     return spreadIndices;
328 }
329
330 // Remove the page number assertions for all but the highest index page with
331 // a given assertion.  Ensures there is only a single page "{pagenum}"
332 // e.g. the last page asserted as page 5 retains that assertion.
333 br.uniquifyPageNums = function() {
334     var seen = {};
335     
336     for (var i = br.pageNums.length - 1; i--; i >= 0) {
337         var pageNum = br.pageNums[i];
338         if ( !seen[pageNum] ) {
339             seen[pageNum] = true;
340         } else {
341             br.pageNums[i] = null;
342         }
343     }
344
345 }
346
347 br.cleanupMetadata = function() {
348     br.uniquifyPageNums();
349 }
350
351 // getEmbedURL
352 //________
353 // Returns a URL for an embedded version of the current book
354 br.getEmbedURL = function(viewParams) {
355     // We could generate a URL hash fragment here but for now we just leave at defaults
356     var url = 'http://' + window.location.host + '/stream/'+this.bookId;
357     if (this.subPrefix != this.bookId) { // Only include if needed
358         url += '/' + this.subPrefix;
359     }
360     url += '?ui=embed';
361     if (typeof(viewParams) != 'undefined') {
362         url += '#' + this.fragmentFromParams(viewParams);
363     }
364     return url;
365 }
366
367 // getEmbedCode
368 //________
369 // Returns the embed code HTML fragment suitable for copy and paste
370 br.getEmbedCode = function(frameWidth, frameHeight, viewParams) {
371     return "<iframe src='" + this.getEmbedURL(viewParams) + "' width='" + frameWidth + "' height='" + frameHeight + "' frameborder='0' ></iframe>";
372 }
373
374 // getOpenLibraryRecord
375 br.getOpenLibraryRecord = function(callback) {
376     // Try looking up by ocaid first, then by source_record
377     
378     var jsonURL = 'http://openlibrary.org/query.json?type=/type/edition&*=&ocaid=' + br.bookId;
379     $.ajax({
380         url: jsonURL,
381         success: function(data) {
382             if (data && data.length > 0) {
383                 callback(br, data[0]);
384             } else {
385                 // try sourceid
386                 jsonURL = 'http://openlibrary.org/query.json?type=/type/edition&*=&source_records=ia:' + br.bookId;
387                 $.ajax({
388                     url: jsonURL,
389                     success: function(data) {
390                         if (data && data.length > 0) {
391                             callback(br, data[0]);
392                         }
393                     },
394                     dataType: 'jsonp'
395                 });
396             }
397         },
398         dataType: 'jsonp'
399     });
400 }
401
402 br.buildInfoDiv = function(jInfoDiv) {
403     // $$$ it might make more sense to have a URL on openlibrary.org that returns this info
404
405     var escapedTitle = BookReader.util.escapeHTML(this.bookTitle);
406     var domainRe = /(\w+\.(com|org))/;
407     var domain = domainRe.exec(this.bookUrl)[1];
408         
409     // $$$ cover looks weird before it loads
410     jInfoDiv.find('.BRfloatCover').append([
411                     '<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('')
412     );
413
414     jInfoDiv.find('.BRfloatMeta').append([
415                     // $$$ description
416                     //'<p>Published ', this.bookPublished,
417                     //, <a href="Open Library Publisher Page">Publisher name</a>',
418                     //'</p>',
419                     //'<p>Written in <a href="Open Library Language page">Language</a></p>',
420                     '<h3>Other Formats</h3>',
421                     '<ul class="links">',
422                         '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '.pdf">PDF</a><span>|</span></li>',
423                         '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '_djvu.txt">Plain Text</a><span>|</span></li>',
424                         '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '_daisy.zip">DAISY</a><span>|</span></li>',
425                         '<li><a href="http://www.archive.org/download/', this.bookId, '/', this.subPrefix, '.epub">ePub</a><span>|</span></li>',
426                         '<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>',
427                     '</ul>',
428                     '<p class="moreInfo"><span></span>More information on <a href="'+ this.bookUrl + '">' + domain + '</a>  </p>'].join('\n'));
429                     
430     jInfoDiv.find('.BRfloatFoot').append([
431                 '<span>|</span>',                
432                 '<a href="http://openlibrary.org/contact" class="problem">Report a problem</a>',
433     ].join('\n'));
434                 
435     if (domain == 'archive.org') {
436         jInfoDiv.find('.BRfloatMeta p.moreInfo span').css(
437             {'background': 'url(http://www.archive.org/favicon.ico) no-repeat', 'width': 22, 'height': 18 }
438         );
439     }
440     
441     jInfoDiv.find('.BRfloatTitle a').attr({'href': this.bookUrl, 'alt': this.bookTitle}).text(this.bookTitle);
442     var bookPath = (window.location + '').replace('#','%23');
443     jInfoDiv.find('a.problem').attr('href','http://openlibrary.org/contact?path=' + bookPath);
444
445 }
446
447 br.pageW =  [
448             <?
449             $i=0;
450             foreach ($scanData->pageData->page as $page) {
451                 if (shouldAddPage($page)) {
452                     if(0 != $i) echo ",";   //stupid IE
453                     echo "{$page->cropBox->w}";
454                     $i++;
455                 }
456             }
457             ?>
458             ];
459
460 br.pageH =  [
461             <?
462             $totalHeight = 0;
463             $i=0;            
464             foreach ($scanData->pageData->page as $page) {
465                 if (shouldAddPage($page)) {
466                     if(0 != $i) echo ",";   //stupid IE                
467                     echo "{$page->cropBox->h}";
468                     $totalHeight += intval($page->cropBox->h/4) + 10;
469                     $i++;
470                 }
471             }
472             ?>
473             ];
474 br.leafMap = [
475             <?
476             $i=0;
477             foreach ($scanData->pageData->page as $page) {
478                 if (shouldAddPage($page)) {
479                     if(0 != $i) echo ",";   //stupid IE
480                     echo "{$page['leafNum']}";
481                     $i++;
482                 }
483             }
484             ?>    
485             ];
486
487 br.pageNums = [
488             <?
489             $i=0;
490             foreach ($scanData->pageData->page as $page) {
491                 if (shouldAddPage($page)) {
492                     if(0 != $i) echo ",";   //stupid IE                
493                     if (array_key_exists('pageNumber', $page) && ('' != $page->pageNumber)) {
494                         echo "'{$page->pageNumber}'";
495                     } else {
496                         echo "null";
497                     }
498                     $i++;
499                 }
500             }
501             ?>    
502             ];
503             
504       
505 br.numLeafs = br.pageW.length;
506
507 br.bookId   = '<?echo $id;?>';
508 br.zip      = '<?echo $imageStackFile;?>';
509 br.subPrefix = '<?echo $subPrefix;?>';
510 br.server   = '<?echo $server;?>';
511 br.bookTitle= '<?echo preg_replace("/\'/", "\\'", $metaData->title);?>';
512 br.bookPath = '<?echo $subItemPath;?>';
513 br.bookUrl  = '<?echo "http://www.archive.org/details/$id";?>';
514 br.imageFormat = '<?echo $imageFormat;?>';
515 br.archiveFormat = '<?echo $archiveFormat;?>';
516
517 <?
518
519 # Load some values from meta.xml
520 if ('' != $metaData->{'page-progression'}) {
521   echo "br.pageProgression = '" . $metaData->{"page-progression"} . "';\n";
522 } else {
523   // Assume page progression is Left To Right
524   echo "br.pageProgression = 'lr';\n";
525 }
526
527 $useOLAuth = false;
528 foreach ($metaData->xpath('//collection') as $collection) {
529     if('browserlending' == $collection) {
530         $useOLAuth = true;
531     }
532 }
533
534 if ($useOLAuth) {
535     echo "br.olAuth = true;\n";
536 } else {
537     echo "br.olAuth = false;\n";
538 }
539
540 # Special cases
541 if ('bandersnatchhsye00scarrich' == $id) {
542     echo "br.mode     = 2;\n";
543     echo "br.auto     = true;\n";
544 }
545
546 ?>
547
548 // Check for config object
549 // $$$ change this to use the newer params object
550 if (typeof(brConfig) != 'undefined') {
551     if (typeof(brConfig["ui"]) != 'undefined') {
552         br.ui = brConfig["ui"];
553     }
554
555     if (brConfig['mode'] == 1) {
556         br.mode = 1;
557         if (typeof(brConfig['reduce'] != 'undefined')) {
558             br.reduce = brConfig['reduce'];
559         }
560     } else if (brConfig['mode'] == 2) {
561         br.mode = 2;      
562     }
563 } // brConfig
564
565
566 function OLAuth() {
567     this.authUrl = 'http://openlibrary.org/ia_auth/' + br.bookId;
568     this.olConnect = false;
569     return this;
570 }
571
572 OLAuth.prototype.init = function() {
573     var htmlStr =  '<p style="text-align:center;"><b>Authenticating in-browser loan with openlibrary.org!</b></p>';
574     htmlStr    +=  '<p>Please wait...</p>';
575
576     this.showPopup("#ddd", "#000", htmlStr);
577     $.ajax({url:this.authUrl + '?rand='+Math.random(), dataType:'jsonp', jsonpCallback:'olAuth.initCallback'});
578 }
579
580 OLAuth.prototype.showPopup = function(bgColor, textColor, msg) {
581     this.popup = document.createElement("div");
582     $(this.popup).css({
583         position: 'absolute',
584         top:      '20px',
585         left:     ($('#BookReader').attr('clientWidth')-400)/2 + 'px',
586         width:    '400px',
587         padding:  "20px",
588         border:   "3px double #999999",
589         zIndex:   3,
590         backgroundColor: bgColor,
591         color: textColor
592     }).appendTo('#BookReader');
593
594     this.popup.innerHTML = msg;
595
596 }
597
598 OLAuth.prototype.initCallback = function(obj) {
599     if (false == obj.success) {
600         $(this.popup).css({
601             backgroundColor: "#f00",
602             color: "#fff"
603         });
604
605         this.popup.innerHTML = obj.msg;
606         return;
607     }
608     
609     //user is authenticated
610     this.setCookie(obj.token);
611     this.olConnect = true;
612     this.startPolling();    
613     br.init();
614 }
615
616 OLAuth.prototype.callback = function(obj) {
617     if (false == obj.success) {
618         this.showPopup("#f00", "#fff", obj.msg);
619         clearInterval(this.poller);
620         this.ttsPoller = null;
621     } else {
622         this.olConnect = true;
623         this.setCookie(obj.token);
624     }
625 }
626
627 OLAuth.prototype.setCookie = function(value) {
628     var date = new Date();
629     date.setTime(date.getTime()+(24*60*60*1000));  //one day expiry
630     var expiry = date.toGMTString();
631     var cookie = 'loan-'+br.bookId+'='+value;
632     cookie    += '; expires='+expiry;
633     cookie    += '; path=/; domain=.archive.org;';
634     document.cookie = cookie; 
635 }
636
637 OLAuth.prototype.startPolling = function () {    
638     var self = this;
639     this.poller=setInterval(function(){
640         if (!self.olConnect) {
641           self.showPopup("#f00", "#fff", 'The BookReader cannot reach Open Library. This might mean that you are offline or that Open Library is down. Please check your Internet connection or try again later.');
642           clearInterval(self.poller);
643           self.ttsPoller = null;        
644         } else {
645           self.olConnect = false;
646           //be sure to add random param to authUrl to avoid stale cache
647           $.ajax({url:self.authUrl+'?rand='+Math.random(), dataType:'jsonp', jsonpCallback:'olAuth.callback'});
648         }
649     },300000);   
650 }
651
652 br.cleanupMetadata();
653 if (br.olAuth) {
654     var olAuth = new OLAuth();
655     olAuth.init();
656 } else {
657     br.init();
658 }
659 <?
660
661
662 function BRFatal($string) {
663     // log error
664     ?>
665     
666     if (typeof(archive_analytics) != 'undefined') {
667         var values = {
668             'bookreader': 'fatal',
669             'description': "<? echo $string; ?>",
670             'itemid': "<? echo $_REQUEST['id']; ?>",
671             'server': "<? echo $_REQUEST['server']; ?>",
672             'request_uri': "<? echo $_SERVER["REQUEST_URI"]; ?>"
673         }
674         
675         if (document.referrer == '') {
676             values['referrer'] = '-';
677         } else {
678             values['referrer'] = document.referrer;
679         }
680         
681         var qs = archive_analytics.format_bug(values);
682
683         var error_img = new Image(100,25);
684         error_img.src = archive_analytics.img_src + "?" + qs;
685     }
686
687     alert("<? echo $string;?>");
688     
689     <?
690     
691     die(-1);
692 }
693
694 // Returns true if a page should be added based on it's information in
695 // the metadata
696 function shouldAddPage($page) {
697     // Return false only if the page is marked addToAccessFormats false.
698     // If there is no assertion we assume it should be added.
699     if (isset($page->addToAccessFormats)) {
700         if ("false" == strtolower(trim($page->addToAccessFormats))) {
701             return false;
702         }
703     }
704     
705     return true;
706 }
707
708 // Returns { 'imageFormat' => , 'archiveFormat' => '} given a sub-item prefix and loaded xml data
709 function findImageStack($subPrefix, $filesData) {
710
711     // $$$ The order of the image formats determines which will be returned first
712     $imageFormats = array('JP2' => 'jp2', 'TIFF' => 'tif', 'JPEG' => 'jpg');
713     $archiveFormats = array('ZIP' => 'zip', 'Tar' => 'tar');
714     $imageGroup = implode('|', array_keys($imageFormats));
715     $archiveGroup = implode('|', array_keys($archiveFormats));
716     // $$$ Currently only return processed images
717     $imageStackRegex = "/Single Page (Processed) (${imageGroup}) (${archiveGroup})/";
718         
719     foreach ($filesData->file as $file) {        
720         if (strpos($file['name'], $subPrefix) === 0) { // subprefix matches beginning
721             if (preg_match($imageStackRegex, $file->format, $matches)) {
722             
723                 // Make sure we have a regular image stack
724                 $imageFormat = $imageFormats[$matches[2]];
725                 if (strpos($file['name'], $subPrefix . '_' . $imageFormat) === 0) {            
726                     return array('imageFormat' => $imageFormat,
727                                  'archiveFormat' => $archiveFormats[$matches[3]],
728                                  'imageStackFile' => $file['name']);
729                 }
730             }
731         }
732     }
733     
734     return array('imageFormat' => 'unknown', 'archiveFormat' => 'unknown', 'imageStackFile' => 'unknown');
735         
736 }
737
738 ?>
739