diff options
Diffstat (limited to 'modules/downloadalbum/controllers/downloadalbum.php')
| -rw-r--r-- | modules/downloadalbum/controllers/downloadalbum.php | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/modules/downloadalbum/controllers/downloadalbum.php b/modules/downloadalbum/controllers/downloadalbum.php new file mode 100644 index 0000000..f352763 --- /dev/null +++ b/modules/downloadalbum/controllers/downloadalbum.php @@ -0,0 +1,300 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2012 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class downloadalbum_Controller extends Controller { + + /** + * Generate a ZIP on-the-fly. + */ + public function zip($container_type, $id) { + switch($container_type) { + case "album": + $container = ORM::factory("item", $id); + if (!$container->is_album()) { + throw new Kohana_Exception('container is not an album: '.$container->relative_path()); + } + + $zipname = (empty($container->name)) + ? 'Gallery.zip' // @todo purified_version_of($container->title).'.zip' + : $container->name.'.zip'; + break; + + case "tag": + // @todo: if the module is not installed, it crash + $container = ORM::factory("tag", $id); + if (is_null($container->name)) { + throw new Kohana_Exception('container is not a tag: '.$id); + } + + $zipname = $container->name.'.zip'; + break; + + default: + throw new Kohana_Exception('unhandled container type: '.$container_type); + } + + $files = $this->getFilesList($container); + + // Calculate ZIP size (look behind for details) + $zipsize = 22; + foreach($files as $f_name => $f_path) { + $zipsize += 76 + 2*strlen($f_name) + filesize($f_path); + } + + // Send headers + $this->prepareOutput(); + $this->sendHeaders($zipname, $zipsize); + + // Generate and send ZIP file + // http://www.pkware.com/documents/casestudies/APPNOTE.TXT (v6.3.2) + $lfh_offset = 0; + $cds = ''; + $cds_offset = 0; + foreach($files as $f_name => $f_path) { + $f_namelen = strlen($f_name); + $f_size = filesize($f_path); + $f_mtime = $this->unix2dostime(filemtime($f_path)); + $f_crc32 = $this->fixBug45028(hexdec(hash_file('crc32b', $f_path, false))); + + // Local file header + echo pack('VvvvVVVVvva' . $f_namelen, + 0x04034b50, // local file header signature (4 bytes) + 0x0a, // version needed to extract (2 bytes) => 1.0 + 0x0800, // general purpose bit flag (2 bytes) => UTF-8 + 0x00, // compression method (2 bytes) => store + $f_mtime, // last mod file time and date (4 bytes) + $f_crc32, // crc-32 (4 bytes) + $f_size, // compressed size (4 bytes) + $f_size, // uncompressed size (4 bytes) + $f_namelen, // file name length (2 bytes) + 0, // extra field length (2 bytes) + + $f_name // file name (variable size) + // extra field (variable size) => n/a + ); + + // File data + readfile($f_path); + + // Data descriptor (n/a) + + // Central directory structure: File header + $cds .= pack('VvvvvVVVVvvvvvVVa' . $f_namelen, + 0x02014b50, // central file header signature (4 bytes) + 0x031e, // version made by (2 bytes) => v3 / Unix + 0x0a, // version needed to extract (2 bytes) => 1.0 + 0x0800, // general purpose bit flag (2 bytes) => UTF-8 + 0x00, // compression method (2 bytes) => store + $f_mtime, // last mod file time and date (4 bytes) + $f_crc32, // crc-32 (4 bytes) + $f_size, // compressed size (4 bytes) + $f_size, // uncompressed size (4 bytes) + $f_namelen, // file name length (2 bytes) + 0, // extra field length (2 bytes) + 0, // file comment length (2 bytes) + 0, // disk number start (2 bytes) + 0, // internal file attributes (2 bytes) + 0x81b40000, // external file attributes (4 bytes) => chmod 664 + $lfh_offset, // relative offset of local header (4 bytes) + + $f_name // file name (variable size) + // extra field (variable size) => n/a + // file comment (variable size) => n/a + ); + + // Update local file header/central directory structure offset + $cds_offset = $lfh_offset += 30 + $f_namelen + $f_size; + } + + // Archive decryption header (n/a) + // Archive extra data record (n/a) + + // Central directory structure: Digital signature (n/a) + echo $cds; // send Central directory structure + + // Zip64 end of central directory record (n/a) + // Zip64 end of central directory locator (n/a) + + // End of central directory record + $numfile = count($files); + $cds_len = strlen($cds); + echo pack('VvvvvVVv', + 0x06054b50, // end of central dir signature (4 bytes) + 0, // number of this disk (2 bytes) + 0, // number of the disk with the start of + // the central directory (2 bytes) + $numfile, // total number of entries in the + // central directory on this disk (2 bytes) + $numfile, // total number of entries in the + // central directory (2 bytes) + $cds_len, // size of the central directory (4 bytes) + $cds_offset, // offset of start of central directory + // with respect to the + // starting disk number (4 bytes) + 0 // .ZIP file comment length (2 bytes) + // .ZIP file comment (variable size) + ); + } + + + /** + * Return the files that must be included in the archive. + */ + private function getFilesList($container) { + $files = array(); + + if( $container instanceof Item_Model && $container->is_album() ) { + $container_realpath = realpath($container->file_path().'/../'); + + $items = $container->viewable() + ->descendants(null, null, array(array("type", "<>", "album"))); + foreach($items as $i) { + if (!access::can('view_full', $i)) { + continue; + } + + $i_realpath = realpath($i->file_path()); + if (!is_readable($i_realpath)) { + continue; + } + + $i_relative_path = str_replace($container_realpath.DIRECTORY_SEPARATOR, '', $i_realpath); + $i_relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $i_relative_path); + $files[$i_relative_path] = $i_realpath; + } + + } else if( $container instanceof Tag_Model ) { + $items = $container->items(); + foreach($items as $i) { + if (!access::can('view_full', $i)) { + continue; + } + + if( $i->is_album() ) { + foreach($this->getFilesList($i) as $f_name => $f_path) { + $files[$container->name.'/'.$f_name] = $f_path; + } + + } else { + $i_realpath = realpath($i->file_path()); + if (!is_readable($i_realpath)) { + continue; + } + + $i_relative_path = $container->name.'/'.$i->name; + $files[$i_relative_path] = $i_realpath; + } + } + } + + if (count($files) === 0) { + throw new Kohana_Exception('no zippable files in ['.$container->name.']'); + } + + return $files; + } + + + /** + * See system/helpers/download.php + */ + private function prepareOutput() { + // Close output buffers + Kohana::close_buffers(FALSE); + // Clear any output + Event::add('system.display', create_function('', 'Kohana::$output = "";')); + } + + /** + * See system/helpers/download.php + */ + private function sendHeaders($filename, $filesize = null) { + if (!is_null($filesize)) { + header('Content-Length: '.$filesize); + } + + // Retrieve MIME type by extension + $mime = Kohana::config('mimes.'.strtolower(substr(strrchr($filename, '.'), 1))); + $mime = empty($mime) ? 'application/octet-stream' : $mime[0]; + header("Content-Type: $mime"); + header('Content-Transfer-Encoding: binary'); + + // Send headers necessary to invoke a "Save As" dialog + header('Content-Disposition: attachment; filename="'.$filename.'"'); + + // Prevent caching + header('Expires: Thu, 01 Jan 1970 00:00:00 GMT'); + + $pragma = 'no-cache'; + $cachecontrol = 'no-cache, max-age=0'; + + // request::user_agent('browser') seems bugged + if (request::user_agent('browser') === 'Internet Explorer' + || stripos(request::user_agent(), 'msie') !== false + || stripos(request::user_agent(), 'internet explorer') !== false) + { + if (request::protocol() === 'https') { + // See http://support.microsoft.com/kb/323308/en-us + $pragma = 'cache'; + $cachecontrol = 'private'; + + } else if (request::user_agent('version') <= '6.0') { + $pragma = ''; + $cachecontrol = 'must-revalidate, post-check=0, pre-check=0'; + } + } + + header('Pragma: '.$pragma); + header('Cache-Control: '.$cachecontrol); + } + + /** + * @return integer DOS date and time + * @param integer _timestamp Unix timestamp + * @desc returns DOS date and time of the timestamp + */ + private function unix2dostime($timestamp) + { + $timebit = getdate($timestamp); + + if ($timebit['year'] < 1980) { + return (1 << 21 | 1 << 16); + } + + $timebit['year'] -= 1980; + + return ($timebit['year'] << 25 | $timebit['mon'] << 21 | + $timebit['mday'] << 16 | $timebit['hours'] << 11 | + $timebit['minutes'] << 5 | $timebit['seconds'] >> 1); + } + + /** + * See http://bugs.php.net/bug.php?id=45028 + */ + private function fixBug45028($hash) { + $output = $hash; + + if( version_compare(PHP_VERSION, '5.2.7', '<') ) { + $str = str_pad(dechex($hash), 8, '0', STR_PAD_LEFT); + $output = hexdec($str{6}.$str{7}.$str{4}.$str{5}.$str{2}.$str{3}.$str{0}.$str{1}); + } + + return $output; + } +} |
