diff options
| author | Tristan Zur <tzur@webserver.ccwn.org> | 2015-06-20 23:56:17 +0200 |
|---|---|---|
| committer | Tristan Zur <tzur@webserver.ccwn.org> | 2015-06-20 23:56:17 +0200 |
| commit | 18a1d682ff18ee69d5c252b013a6a6935cd9b5fe (patch) | |
| tree | b1da62c8b7d03341a7b74b3fbb533e6391950be0 | |
| parent | b69c03794f80aa811f0613cf4b802619e7ecdbdd (diff) | |
New modules added
93 files changed, 14913 insertions, 0 deletions
diff --git a/modules/autorotate/helpers/autorotate.php b/modules/autorotate/helpers/autorotate.php new file mode 100644 index 0000000..cb0e249 --- /dev/null +++ b/modules/autorotate/helpers/autorotate.php @@ -0,0 +1,68 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 autorotate { + static function rotate_item($item) { + + require_once(MODPATH . 'autorotate/lib/pel/PelDataWindow.php'); + require_once(MODPATH . 'autorotate/lib/pel/PelJpeg.php'); + require_once(MODPATH . 'autorotate/lib/pel/PelTiff.php'); + + // Only try to rotate photos based on EXIF + if ($item->is_photo() && $item->mime_type == "image/jpeg") { + require_once(MODPATH . "exif/lib/exif.php"); + $exif_raw = read_exif_data_raw($item->file_path(), false); + if (isset($exif_raw['ValidEXIFData'])) { + $orientation = $exif_raw["IFD0"]["Orientation"]; + $degrees = 0; + if ($orientation == '3: Upside-down') { + $degrees = 180; + } + else if ($orientation == '8: 90 deg CW') { + $degrees = -90; + } + else if ($orientation == '6: 90 deg CCW') { + $degrees = 90; + } + if($degrees) { + $tmpfile = tempnam(TMPPATH, "rotate"); + gallery_graphics::rotate($item->file_path(), $tmpfile, array("degrees" => $degrees)); + // Update EXIF info + $data = new PelDataWindow(file_get_contents($tmpfile)); + if (PelJpeg::isValid($data)) { + $jpeg = $file = new PelJpeg(); + $jpeg->load($data); + $exif = $jpeg->getExif(); + if($exif !== null) { + $tiff = $exif->getTiff(); + $ifd0 = $tiff->getIfd(); + $orientation = $ifd0->getEntry(PelTag::ORIENTATION); + $orientation->setValue(1); + file_put_contents($tmpfile, $file->getBytes()); + } + } + $item->set_data_file($tmpfile); + $item->save(); + unlink($tmpfile); + } + } + } + return; + } +}
\ No newline at end of file diff --git a/modules/autorotate/helpers/autorotate_event.php b/modules/autorotate/helpers/autorotate_event.php new file mode 100644 index 0000000..c2fd04b --- /dev/null +++ b/modules/autorotate/helpers/autorotate_event.php @@ -0,0 +1,32 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 autorotate_event_Core { + // The assumption is that the exception was logged at a lower level, but we + // don't want to screw up the processing that was generating the notification + // so we don't pass the exception up the call stack + static function item_created($item) { + try { + autorotate::rotate_item($item); + } catch (Exception $e) { + Kohana_Log::add("error", "@todo autorotate_event::item_created() failed"); + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + } + } +}
\ No newline at end of file diff --git a/modules/autorotate/helpers/autorotate_installer.php b/modules/autorotate/helpers/autorotate_installer.php new file mode 100644 index 0000000..c3ed03a --- /dev/null +++ b/modules/autorotate/helpers/autorotate_installer.php @@ -0,0 +1,42 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 autorotate_installer { + // static function install() { + // module::set_version("autorotate", 2); + // } + + // static function upgrade($version) { + // if ($version == 1) { + // module::set_version("autorotate", $version = 2); + // } + // } + + static function deactivate() { + site_status::clear("autorotate_needs_exif"); + } + + static function can_activate() { + $messages = array(); + if (!module::is_active("exif")) { + $messages["warn"][] = t("The autorotate module requires the EXIF module."); + } + return $messages; + } +}
\ No newline at end of file diff --git a/modules/autorotate/lib/pel/Pel.php b/modules/autorotate/lib/pel/Pel.php new file mode 100644 index 0000000..3ac8468 --- /dev/null +++ b/modules/autorotate/lib/pel/Pel.php @@ -0,0 +1,372 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006, 2007 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Miscellaneous stuff for the overall behavior of PEL. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + + +/* Initialize Gettext, if available. This must be done before any + * part of PEL calls Pel::tra() or Pel::fmt() --- this is ensured if + * every piece of code using those two functions require() this file. + * + * If Gettext is not available, wrapper functions will be created, + * allowing PEL to function, but without any translations. + * + * The PEL translations are stored in './locale'. It is important to + * use an absolute path here because the lookups will be relative to + * the current directory. */ + +if (function_exists('dgettext')) { + bindtextdomain('pel', dirname(__FILE__) . '/locale'); +} else { + + /** + * Pretend to lookup a message in a specific domain. + * + * This is just a stub which will return the original message + * untranslated. The function will only be defined if the Gettext + * extension has not already defined it. + * + * @param string $domain the domain. + * + * @param string $str the message to be translated. + * + * @return string the original, untranslated message. + */ + function dgettext($domain, $str) { + return $str; + } +} + + +/** + * Class with miscellaneous static methods. + * + * This class will contain various methods that govern the overall + * behavior of PEL. + * + * Debugging output from PEL can be turned on and off by assigning + * true or false to {@link Pel::$debug}. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class Pel { + + /** + * Flag for controlling debug information. + * + * The methods producing debug information ({@link debug()} and + * {@link warning()}) will only output something if this variable is + * set to true. + * + * @var boolean + */ + private static $debug = false; + + /** + * Flag for strictness of parsing. + * + * If this variable is set to true, then most errors while loading + * images will result in exceptions being thrown. Otherwise a + * warning will be emitted (using {@link Pel::warning}) and the + * exceptions will be appended to {@link Pel::$exceptions}. + * + * Some errors will still be fatal and result in thrown exceptions, + * but an effort will be made to skip over as much garbage as + * possible. + * + * @var boolean + */ + private static $strict = false; + + /** + * Stored exceptions. + * + * When {@link Pel::$strict} is set to false exceptions will be + * accumulated here instead of being thrown. + */ + private static $exceptions = array(); + + /** + * Quality setting for encoding JPEG images. + * + * This controls the quality used then PHP image resources are + * encoded into JPEG images. This happens when you create a + * {@link PelJpeg} object based on an image resource. + * + * The default is 75 for average quality images, but you can change + * this to an integer between 0 and 100. + * + * @var int + */ + private static $quality = 75; + + + /** + * Set the JPEG encoding quality. + * + * @param int $quality an integer between 0 and 100 with 75 being + * average quality and 95 very good quality. + */ + function setJPEGQuality($quality) { + self::$quality = $quality; + } + + + /** + * Get current setting for JPEG encoding quality. + * + * @return int the quality. + */ + function getJPEGQuality() { + return self::$quality; + } + + + /** + * Return list of stored exceptions. + * + * When PEL is parsing in non-strict mode, it will store most + * exceptions instead of throwing them. Use this method to get hold + * of them when a call returns. + * + * Code for using this could look like this: + * + * <code> + * Pel::setStrictParsing(true); + * Pel::clearExceptions(); + * + * $jpeg = new PelJpeg($file); + * + * // Check for exceptions. + * foreach (Pel::getExceptions() as $e) { + * printf("Exception: %s\n", $e->getMessage()); + * if ($e instanceof PelEntryException) { + * // Warn about entries that couldn't be loaded. + * printf("Warning: Problem with %s.\n", + * PelTag::getName($e->getType(), $e->getTag())); + * } + * } + * </code> + * + * This gives applications total control over the amount of error + * messages shown and (hopefully) provides the necessary information + * for proper error recovery. + * + * @return array the exceptions. + */ + static function getExceptions() { + return self::$exceptions; + } + + + /** + * Clear list of stored exceptions. + * + * Use this function before a call to some method if you intend to + * check for exceptions afterwards. + */ + static function clearExceptions() { + self::$exceptions = array(); + } + + + /** + * Conditionally throw an exception. + * + * This method will throw the passed exception when strict parsing + * in effect (see {@link setStrictParsing()}). Otherwise the + * exception is stored (it can be accessed with {@link + * getExceptions()}) and a warning is issued (with {@link + * Pel::warning}). + * + * @param PelException $e the exceptions. + */ + static function maybeThrow(PelException $e) { + if (self::$strict) { + throw $e; + } else { + self::$exceptions[] = $e; + self::warning('%s (%s:%s)', $e->getMessage(), + basename($e->getFile()), $e->getLine()); + } + } + + + /** + * Enable/disable strict parsing. + * + * If strict parsing is enabled, then most errors while loading + * images will result in exceptions being thrown. Otherwise a + * warning will be emitted (using {@link Pel::warning}) and the + * exceptions will be stored for later use via {@link + * getExceptions()}. + * + * Some errors will still be fatal and result in thrown exceptions, + * but an effort will be made to skip over as much garbage as + * possible. + * + * @param boolean $flag use true to enable strict parsing, false to + * diable. + */ + function setStrictParsing($flag) { + self::$strict = $flag; + } + + + /** + * Get current setting for strict parsing. + * + * @return boolean true if strict parsing is in effect, false + * otherwise. + */ + function getStrictParsing() { + return self::$strict; + } + + + /** + * Enable/disable debugging output. + * + * @param boolean $flag use true to enable debug output, false to + * diable. + */ + function setDebug($flag) { + self::$debug = $flag; + } + + + /** + * Get current setting for debug output. + * + * @return boolean true if debug is enabled, false otherwise. + */ + function getDebug() { + return self::$debug; + } + + + /** + * Conditionally output debug information. + * + * This method works just like printf() except that it always + * terminates the output with a newline, and that it only outputs + * something if the {@link Pel::$debug} is true. + * + * @param string $format the format string. + * + * @param mixed $args,... any number of arguments can be given. The + * arguments will be available for the format string as usual with + * sprintf(). + */ + static function debug() { + if (self::$debug) { + $args = func_get_args(); + $str = array_shift($args); + vprintf($str . "\n", $args); + } + } + + + /** + * Conditionally output a warning. + * + * This method works just like printf() except that it prepends the + * output with the string 'Warning: ', terminates the output with a + * newline, and that it only outputs something if the PEL_DEBUG + * defined to some true value. + * + * @param string $format the format string. + * + * @param mixed $args,... any number of arguments can be given. The + * arguments will be available for the format string as usual with + * sprintf(). + */ + static function warning() { + if (self::$debug) { + $args = func_get_args(); + $str = array_shift($args); + vprintf('Warning: ' . $str . "\n", $args); + } + } + + + /** + * Translate a string. + * + * This static function will use Gettext to translate a string. By + * always using this function for static string one is assured that + * the translation will be taken from the correct text domain. + * Dynamic strings should be passed to {@link fmt} instead. + * + * @param string the string that should be translated. + * + * @return string the translated string, or the original string if + * no translation could be found. + */ + static function tra($str) { + return dgettext('pel', $str); + } + + + /** + * Translate and format a string. + * + * This static function will first use Gettext to translate a format + * string, which will then have access to any extra arguments. By + * always using this function for dynamic string one is assured that + * the translation will be taken from the correct text domain. If + * the string is static, use {@link tra} instead as it will be + * faster. + * + * @param string $format the format string. This will be translated + * before being used as a format string. + * + * @param mixed $args,... any number of arguments can be given. The + * arguments will be available for the format string as usual with + * sprintf(). + * + * @return string the translated string, or the original string if + * no translation could be found. + */ + static function fmt() { + $args = func_get_args(); + $str = array_shift($args); + return vsprintf(dgettext('pel', $str), $args); + } + +} + diff --git a/modules/autorotate/lib/pel/PelConvert.php b/modules/autorotate/lib/pel/PelConvert.php new file mode 100644 index 0000000..0a7c2c5 --- /dev/null +++ b/modules/autorotate/lib/pel/PelConvert.php @@ -0,0 +1,397 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Routines for converting back and forth between bytes and integers. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/** + * Conversion functions to and from bytes and integers. + * + * The functions found in this class are used to convert bytes into + * integers of several sizes ({@link bytesToShort}, {@link + * bytesToLong}, and {@link bytesToRational}) and convert integers of + * several sizes into bytes ({@link shortToBytes} and {@link + * longToBytes}). + * + * All the methods are static and they all rely on an argument that + * specifies the byte order to be used, this must be one of the class + * constants {@link LITTLE_ENDIAN} or {@link BIG_ENDIAN}. These + * constants will be referred to as the pseudo type PelByteOrder + * throughout the documentation. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelConvert { + + /** + * Little-endian (Intel) byte order. + * + * Data stored in little-endian byte order store the least + * significant byte first, so the number 0x12345678 becomes 0x78 + * 0x56 0x34 0x12 when stored with little-endian byte order. + */ + const LITTLE_ENDIAN = true; + + /** + * Big-endian (Motorola) byte order. + * + * Data stored in big-endian byte order store the most significant + * byte first, so the number 0x12345678 becomes 0x12 0x34 0x56 0x78 + * when stored with big-endian byte order. + */ + const BIG_ENDIAN = false; + + + /** + * Convert an unsigned short into two bytes. + * + * @param int the unsigned short that will be converted. The lower + * two bytes will be extracted regardless of the actual size passed. + * + * @param PelByteOrder one of {@link LITTLE_ENDIAN} and {@link + * BIG_ENDIAN}. + * + * @return string the bytes representing the unsigned short. + */ + static function shortToBytes($value, $endian) { + if ($endian == self::LITTLE_ENDIAN) + return chr($value) . chr($value >> 8); + else + return chr($value >> 8) . chr($value); + } + + + /** + * Convert a signed short into two bytes. + * + * @param int the signed short that will be converted. The lower + * two bytes will be extracted regardless of the actual size passed. + * + * @param PelByteOrder one of {@link LITTLE_ENDIAN} and {@link + * BIG_ENDIAN}. + * + * @return string the bytes representing the signed short. + */ + static function sShortToBytes($value, $endian) { + /* We can just use shortToBytes, since signed shorts fits well + * within the 32 bit signed integers used in PHP. */ + return self::shortToBytes($value, $endian); + } + + + /** + * Convert an unsigned long into four bytes. + * + * Because PHP limits the size of integers to 32 bit signed, one + * cannot really have an unsigned integer in PHP. But integers + * larger than 2^31-1 will be promoted to 64 bit signed floating + * point numbers, and so such large numbers can be handled too. + * + * @param int the unsigned long that will be converted. The + * argument will be treated as an unsigned 32 bit integer and the + * lower four bytes will be extracted. Treating the argument as an + * unsigned integer means that the absolute value will be used. Use + * {@link sLongToBytes} to convert signed integers. + * + * @param PelByteOrder one of {@link LITTLE_ENDIAN} and {@link + * BIG_ENDIAN}. + * + * @return string the bytes representing the unsigned long. + */ + static function longToBytes($value, $endian) { + /* We cannot convert the number to bytes in the normal way (using + * shifts and modulo calculations) because the PHP operator >> and + * function chr() clip their arguments to 2^31-1, which is the + * largest signed integer known to PHP. But luckily base_convert + * handles such big numbers. */ + $hex = str_pad(base_convert($value, 10, 16), 8, '0', STR_PAD_LEFT); + if ($endian == self::LITTLE_ENDIAN) + return (chr(hexdec($hex{6} . $hex{7})) . + chr(hexdec($hex{4} . $hex{5})) . + chr(hexdec($hex{2} . $hex{3})) . + chr(hexdec($hex{0} . $hex{1}))); + else + return (chr(hexdec($hex{0} . $hex{1})) . + chr(hexdec($hex{2} . $hex{3})) . + chr(hexdec($hex{4} . $hex{5})) . + chr(hexdec($hex{6} . $hex{7}))); + } + + + /** + * Convert a signed long into four bytes. + * + * @param int the signed long that will be converted. The argument + * will be treated as a signed 32 bit integer, from which the lower + * four bytes will be extracted. + * + * @param PelByteOrder one of {@link LITTLE_ENDIAN} and {@link + * BIG_ENDIAN}. + * + * @return string the bytes representing the signed long. + */ + static function sLongToBytes($value, $endian) { + /* We can convert the number into bytes in the normal way using + * shifts and modulo calculations here (in contrast with + * longToBytes) because PHP automatically handles 32 bit signed + * integers for us. */ + if ($endian == self::LITTLE_ENDIAN) + return (chr($value) . + chr($value >> 8) . + chr($value >> 16) . + chr($value >> 24)); + else + return (chr($value >> 24) . + chr($value >> 16) . + chr($value >> 8) . + chr($value)); + } + + + /** + * Extract an unsigned byte from a string of bytes. + * + * @param string the bytes. + * + * @param int the offset. The byte found at the offset will be + * returned as an integer. The must be at least one byte available + * at offset. + * + * @return int the unsigned byte found at offset, e.g., an integer + * in the range 0 to 255. + */ + static function bytesToByte($bytes, $offset) { + return ord($bytes{$offset}); + } + + + /** + * Extract a signed byte from bytes. + * + * @param string the bytes. + * + * @param int the offset. The byte found at the offset will be + * returned as an integer. The must be at least one byte available + * at offset. + * + * @return int the signed byte found at offset, e.g., an integer in + * the range -128 to 127. + */ + static function bytesToSByte($bytes, $offset) { + $n = self::bytesToByte($bytes, $offset); + if ($n > 127) + return $n - 256; + else + return $n; + } + + + /** + * Extract an unsigned short from bytes. + * + * @param string the bytes. + * + * @param int the offset. The short found at the offset will be + * returned as an integer. There must be at least two bytes + * available beginning at the offset given. + * + * @return int the unsigned short found at offset, e.g., an integer + * in the range 0 to 65535. + * + * @param PelByteOrder one of {@link LITTLE_ENDIAN} and {@link + * BIG_ENDIAN}. + */ + static function bytesToShort($bytes, $offset, $endian) { + if ($endian == self::LITTLE_ENDIAN) + return (ord($bytes{$offset+1}) * 256 + + ord($bytes{$offset})); + else + return (ord($bytes{$offset}) * 256 + + ord($bytes{$offset+1})); + } + + + /** + * Extract a signed short from bytes. + * + * @param string the bytes. + * + * @param int the offset. The short found at offset will be returned + * as an integer. There must be at least two bytes available + * beginning at the offset given. + * + * @return int the signed byte found at offset, e.g., an integer in + * the range -32768 to 32767. + * + * @param PelByteOrder one of {@link LITTLE_ENDIAN} and {@link + * BIG_ENDIAN}. + */ + static function bytesToSShort($bytes, $offset, $endian) { + $n = self::bytesToShort($bytes, $offset, $endian); + if ($n > 32767) + return $n - 65536; + else + return $n; + } + + + /** + * Extract an unsigned long from bytes. + * + * @param string the bytes. + * + * @param int the offset. The long found at offset will be returned + * as an integer. There must be at least four bytes available + * beginning at the offset given. + * + * @return int the unsigned long found at offset, e.g., an integer + * in the range 0 to 4294967295. + * + * @param PelByteOrder one of {@link LITTLE_ENDIAN} and {@link + * BIG_ENDIAN}. + */ + static function bytesToLong($bytes, $offset, $endian) { + if ($endian == self::LITTLE_ENDIAN) + return (ord($bytes{$offset+3}) * 16777216 + + ord($bytes{$offset+2}) * 65536 + + ord($bytes{$offset+1}) * 256 + + ord($bytes{$offset})); + else + return (ord($bytes{$offset}) * 16777216 + + ord($bytes{$offset+1}) * 65536 + + ord($bytes{$offset+2}) * 256 + + ord($bytes{$offset+3})); + } + + + /** + * Extract a signed long from bytes. + * + * @param string the bytes. + * + * @param int the offset. The long found at offset will be returned + * as an integer. There must be at least four bytes available + * beginning at the offset given. + * + * @return int the signed long found at offset, e.g., an integer in + * the range -2147483648 to 2147483647. + * + * @param PelByteOrder one of {@link LITTLE_ENDIAN} and {@link + * BIG_ENDIAN}. + */ + static function bytesToSLong($bytes, $offset, $endian) { + $n = self::bytesToLong($bytes, $offset, $endian); + if ($n > 2147483647) + return $n - 4294967296; + else + return $n; + } + + + /** + * Extract an unsigned rational from bytes. + * + * @param string the bytes. + * + * @param int the offset. The rational found at offset will be + * returned as an array. There must be at least eight bytes + * available beginning at the offset given. + * + * @return array the unsigned rational found at offset, e.g., an + * array with two integers in the range 0 to 4294967295. + * + * @param PelByteOrder one of {@link LITTLE_ENDIAN} and {@link + * BIG_ENDIAN}. + */ + static function bytesToRational($bytes, $offset, $endian) { + return array(self::bytesToLong($bytes, $offset, $endian), + self::bytesToLong($bytes, $offset+4, $endian)); + } + + + /** + * Extract a signed rational from bytes. + * + * @param string the bytes. + * + * @param int the offset. The rational found at offset will be + * returned as an array. There must be at least eight bytes + * available beginning at the offset given. + * + * @return array the signed rational found at offset, e.g., an array + * with two integers in the range -2147483648 to 2147483647. + * + * @param PelByteOrder one of {@link LITTLE_ENDIAN} and {@link + * BIG_ENDIAN}. + */ + static function bytesToSRational($bytes, $offset, $endian) { + return array(self::bytesToSLong($bytes, $offset, $endian), + self::bytesToSLong($bytes, $offset+4, $endian)); + } + + + /** + * Format bytes for dumping. + * + * This method is for debug output, it will format a string as a + * hexadecimal dump suitable for display on a terminal. The output + * is printed directly to standard out. + * + * @param string the bytes that will be dumped. + * + * @param int the maximum number of bytes to dump. If this is left + * out (or left to the default of 0), then the entire string will be + * dumped. + */ + static function bytesToDump($bytes, $max = 0) { + $s = strlen($bytes); + + if ($max > 0) + $s = min($max, $s); + + $line = 24; + + for ($i = 0; $i < $s; $i++) { + printf('%02X ', ord($bytes{$i})); + + if (($i+1) % $line == 0) + print("\n"); + } + print("\n"); + } + +} + diff --git a/modules/autorotate/lib/pel/PelDataWindow.php b/modules/autorotate/lib/pel/PelDataWindow.php new file mode 100644 index 0000000..b40722c --- /dev/null +++ b/modules/autorotate/lib/pel/PelDataWindow.php @@ -0,0 +1,540 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006, 2007 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * A container for bytes with a limited window of accessible bytes. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelException.php'); +require_once('PelConvert.php'); +/**#@-*/ + + +/** + * An exception thrown when an invalid offset is encountered. + * + * @package PEL + * @subpackage Exception + */ +class PelDataWindowOffsetException extends PelException {} + +/** + * An exception thrown when an invalid window is encountered. + * + * @package PEL + * @subpackage Exception + */ +class PelDataWindowWindowException extends PelException {} + +/** + * The window. + * + * @package PEL + */ +class PelDataWindow { + + /** + * The data held by this window. + * + * The string can contain any kind of data, including binary data. + * + * @var string + */ + private $data = ''; + + /** + * The byte order currently in use. + * + * This will be the byte order used when data is read using the for + * example the {@link getShort} function. It must be one of {@link + * PelConvert::LITTLE_ENDIAN} and {@link PelConvert::BIG_ENDIAN}. + * + * @var PelByteOrder + * @see setByteOrder, getByteOrder + */ + private $order; + + /** + * The start of the current window. + * + * All offsets used for access into the data will count from this + * offset, effectively limiting access to a window starting at this + * byte. + * + * @var int + * @see setWindowStart + */ + private $start = 0; + + /** + * The size of the current window. + * + * All offsets used for access into the data will be limited by this + * variable. A valid offset must be strictly less than this + * variable. + * + * @var int + * @see setWindowSize + */ + private $size = 0; + + + /** + * Construct a new data window with the data supplied. + * + * @param mixed the data that this window will contain. This can + * either be given as a string (interpreted litteraly as a sequence + * of bytes) or a PHP image resource handle. The data will be copied + * into the new data window. + * + * @param boolean the initial byte order of the window. This must + * be either {@link PelConvert::LITTLE_ENDIAN} or {@link + * PelConvert::BIG_ENDIAN}. This will be used when integers are + * read from the data, and it can be changed later with {@link + * setByteOrder()}. + */ + function __construct($data = '', $endianess = PelConvert::LITTLE_ENDIAN) { + if (is_string($data)) { + $this->data = $data; + } elseif (is_resource($data) && get_resource_type($data) == 'gd') { + /* The ImageJpeg() function insists on printing the bytes + * instead of returning them in a more civil way as a string, so + * we have to buffer the output... */ + ob_start(); + ImageJpeg($data, null, Pel::getJPEGQuality()); + $this->data = ob_get_clean(); + } else { + throw new PelInvalidArgumentException('Bad type for $data: %s', + gettype($data)); + } + + $this->order = $endianess; + $this->size = strlen($this->data); + } + + + /** + * Get the size of the data window. + * + * @return int the number of bytes covered by the window. The + * allowed offsets go from 0 up to this number minus one. + * + * @see getBytes() + */ + function getSize() { + return $this->size; + } + + + /** + * Change the byte order of the data. + * + * @param PelByteOrder the new byte order. This must be either + * {@link PelConvert::LITTLE_ENDIAN} or {@link + * PelConvert::BIG_ENDIAN}. + */ + function setByteOrder($o) { + $this->order = $o; + } + + + /** + * Get the currently used byte order. + * + * @return PelByteOrder this will be either {@link + * PelConvert::LITTLE_ENDIAN} or {@link PelConvert::BIG_ENDIAN}. + */ + function getByteOrder() { + return $this->order; + } + + + /* Move the start of the window forward. + * + * @param int the new start of the window. All new offsets will be + * calculated from this new start offset, and the size of the window + * will shrink to keep the end of the window in place. + */ + function setWindowStart($start) { + if ($start < 0 || $start > $this->size) + throw new PelDataWindowWindowException('Window [%d, %d] does ' . + 'not fit in window [0, %d]', + $start, $this->size, $this->size); + + $this->start += $start; + $this->size -= $start; + } + + + /** + * Adjust the size of the window. + * + * The size can only be made smaller. + * + * @param int the desired size of the window. If the argument is + * negative, the window will be shrunk by the argument. + */ + function setWindowSize($size) { + if ($size < 0) + $size += $this->size; + + if ($size < 0 || $size > $this->size) + throw new PelDataWindowWindowException('Window [0, %d] ' . + 'does not fit in window [0, %d]', + $size, $this->size); + $this->size = $size; + } + + + /** + * Make a new data window with the same data as the this window. + * + * @param mixed if an integer is supplied, then it will be the start + * of the window in the clone. If left unspecified, then the clone + * will inherit the start from this object. + * + * @param mixed if an integer is supplied, then it will be the size + * of the window in the clone. If left unspecified, then the clone + * will inherit the size from this object. + * + * @return PelDataWindow a new window that operates on the same data + * as this window, but (optionally) with a smaller window size. + */ + function getClone($start = false, $size = false) { + $c = clone $this; + + if (is_int($start)) + $c->setWindowStart($start); + + if (is_int($size)) + $c->setWindowSize($size); + + return $c; + } + + + /** + * Validate an offset against the current window. + * + * @param int the offset to be validated. If the offset is negative + * or if it is greater than or equal to the current window size, + * then a {@link PelDataWindowOffsetException} is thrown. + * + * @return void if the offset is valid nothing is returned, if it is + * invalid a new {@link PelDataWindowOffsetException} is thrown. + */ + private function validateOffset($o) { + if ($o < 0 || $o >= $this->size) + throw new PelDataWindowOffsetException('Offset %d not within [%d, %d]', + $o, 0, $this->size-1); + } + + + /** + * Return some or all bytes visible in the window. + * + * This method works just like the standard {@link substr()} + * function in PHP with the exception that it works within the + * window of accessible bytes and does strict range checking. + * + * @param int the offset to the first byte returned. If a negative + * number is given, then the counting will be from the end of the + * window. Invalid offsets will result in a {@link + * PelDataWindowOffsetException} being thrown. + * + * @param int the size of the sub-window. If a negative number is + * given, then that many bytes will be omitted from the result. + * + * @return string a subset of the bytes in the window. This will + * always return no more than {@link getSize()} bytes. + */ + function getBytes($start = false, $size = false) { + if (is_int($start)) { + if ($start < 0) + $start += $this->size; + + $this->validateOffset($start); + } else { + $start = 0; + } + + if (is_int($size)) { + if ($size <= 0) + $size += $this->size - $start; + + $this->validateOffset($start+$size); + } else { + $size = $this->size - $start; + } + + return substr($this->data, $this->start + $start, $size); + } + + + /** + * Return an unsigned byte from the data. + * + * @param int the offset into the data. An offset of zero will + * return the first byte in the current allowed window. The last + * valid offset is equal to {@link getSize()}-1. Invalid offsets + * will result in a {@link PelDataWindowOffsetException} being + * thrown. + * + * @return int the unsigned byte found at offset. + */ + function getByte($o = 0) { + /* Validate the offset --- this throws an exception if offset is + * out of range. */ + $this->validateOffset($o); + + /* Translate the offset into an offset into the data. */ + $o += $this->start; + + /* Return an unsigned byte. */ + return PelConvert::bytesToByte($this->data, $o); + } + + + /** + * Return a signed byte from the data. + * + * @param int the offset into the data. An offset of zero will + * return the first byte in the current allowed window. The last + * valid offset is equal to {@link getSize()}-1. Invalid offsets + * will result in a {@link PelDataWindowOffsetException} being + * thrown. + * + * @return int the signed byte found at offset. + */ + function getSByte($o = 0) { + /* Validate the offset --- this throws an exception if offset is + * out of range. */ + $this->validateOffset($o); + + /* Translate the offset into an offset into the data. */ + $o += $this->start; + + /* Return a signed byte. */ + return PelConvert::bytesToSByte($this->data, $o); + } + + + /** + * Return an unsigned short read from the data. + * + * @param int the offset into the data. An offset of zero will + * return the first short available in the current allowed window. + * The last valid offset is equal to {@link getSize()}-2. Invalid + * offsets will result in a {@link PelDataWindowOffsetException} + * being thrown. + * + * @return int the unsigned short found at offset. + */ + function getShort($o = 0) { + /* Validate the offset+1 to see if we can safely get two bytes --- + * this throws an exception if offset is out of range. */ + $this->validateOffset($o); + $this->validateOffset($o+1); + + /* Translate the offset into an offset into the data. */ + $o += $this->start; + + /* Return an unsigned short. */ + return PelConvert::bytesToShort($this->data, $o, $this->order); + } + + + /** + * Return a signed short read from the data. + * + * @param int the offset into the data. An offset of zero will + * return the first short available in the current allowed window. + * The last valid offset is equal to {@link getSize()}-2. Invalid + * offsets will result in a {@link PelDataWindowOffsetException} + * being thrown. + * + * @return int the signed short found at offset. + */ + function getSShort($o = 0) { + /* Validate the offset+1 to see if we can safely get two bytes --- + * this throws an exception if offset is out of range. */ + $this->validateOffset($o); + $this->validateOffset($o+1); + + /* Translate the offset into an offset into the data. */ + $o += $this->start; + + /* Return a signed short. */ + return PelConvert::bytesToSShort($this->data, $o, $this->order); + } + + + /** + * Return an unsigned long read from the data. + * + * @param int the offset into the data. An offset of zero will + * return the first long available in the current allowed window. + * The last valid offset is equal to {@link getSize()}-4. Invalid + * offsets will result in a {@link PelDataWindowOffsetException} + * being thrown. + * + * @return int the unsigned long found at offset. + */ + function getLong($o = 0) { + /* Validate the offset+3 to see if we can safely get four bytes + * --- this throws an exception if offset is out of range. */ + $this->validateOffset($o); + $this->validateOffset($o+3); + + /* Translate the offset into an offset into the data. */ + $o += $this->start; + + /* Return an unsigned long. */ + return PelConvert::bytesToLong($this->data, $o, $this->order); + } + + + /** + * Return a signed long read from the data. + * + * @param int the offset into the data. An offset of zero will + * return the first long available in the current allowed window. + * The last valid offset is equal to {@link getSize()}-4. Invalid + * offsets will result in a {@link PelDataWindowOffsetException} + * being thrown. + * + * @return int the signed long found at offset. + */ + function getSLong($o = 0) { + /* Validate the offset+3 to see if we can safely get four bytes + * --- this throws an exception if offset is out of range. */ + $this->validateOffset($o); + $this->validateOffset($o+3); + + /* Translate the offset into an offset into the data. */ + $o += $this->start; + + /* Return a signed long. */ + return PelConvert::bytesToSLong($this->data, $o, $this->order); + } + + + /** + * Return an unsigned rational read from the data. + * + * @param int the offset into the data. An offset of zero will + * return the first rational available in the current allowed + * window. The last valid offset is equal to {@link getSize()}-8. + * Invalid offsets will result in a {@link + * PelDataWindowOffsetException} being thrown. + * + * @return array the unsigned rational found at offset. A rational + * number is represented as an array of two numbers: the enumerator + * and denominator. Both of these numbers will be unsigned longs. + */ + function getRational($o = 0) { + return array($this->getLong($o), $this->getLong($o+4)); + } + + + /** + * Return a signed rational read from the data. + * + * @param int the offset into the data. An offset of zero will + * return the first rational available in the current allowed + * window. The last valid offset is equal to {@link getSize()}-8. + * Invalid offsets will result in a {@link + * PelDataWindowOffsetException} being thrown. + * + * @return array the signed rational found at offset. A rational + * number is represented as an array of two numbers: the enumerator + * and denominator. Both of these numbers will be signed longs. + */ + function getSRational($o = 0) { + return array($this->getSLong($o), $this->getSLong($o+4)); + } + + + /** + * String comparison on substrings. + * + * @param int the offset into the data. An offset of zero will make + * the comparison start with the very first byte available in the + * window. The last valid offset is equal to {@link getSize()} + * minus the length of the string. If the string is too long, then + * a {@link PelDataWindowOffsetException} will be thrown. + * + * @param string the string to compare with. + * + * @return boolean true if the string given matches the data in the + * window, at the specified offset, false otherwise. The comparison + * will stop as soon as a mismatch if found. + */ + function strcmp($o, $str) { + /* Validate the offset of the final character we might have to + * check. */ + $s = strlen($str); + $this->validateOffset($o); + $this->validateOffset($o + $s - 1); + + /* Translate the offset into an offset into the data. */ + $o += $this->start; + + /* Check each character, return as soon as the answer is known. */ + for ($i = 0; $i < $s; $i++) { + if ($this->data{$o + $i} != $str{$i}) + return false; + } + + /* All characters matches each other, return true. */ + return true; + } + + + /** + * Return a string representation of the data window. + * + * @return string a description of the window with information about + * the number of bytes accessible, the total number of bytes, and + * the window start and stop. + */ + function __toString() { + return Pel::fmt('DataWindow: %d bytes in [%d, %d] of %d bytes', + $this->size, + $this->start, $this->start + $this->size, + strlen($this->data)); + } + +} + diff --git a/modules/autorotate/lib/pel/PelEntry.php b/modules/autorotate/lib/pel/PelEntry.php new file mode 100644 index 0000000..00af8d3 --- /dev/null +++ b/modules/autorotate/lib/pel/PelEntry.php @@ -0,0 +1,382 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes for dealing with Exif entries. + * + * This file defines two exception classes and the abstract class + * {@link PelEntry} which provides the basic methods that all Exif + * entries will have. All Exif entries will be represented by + * descendants of the {@link PelEntry} class --- the class itself is + * abstract and so it cannot be instantiated. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelException.php'); +require_once('PelFormat.php'); +require_once('PelTag.php'); +require_once('Pel.php'); +/**#@-*/ + + +/** + * Exception indicating a problem with an entry. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + * @subpackage Exception + */ +class PelEntryException extends PelException { + + /** + * The IFD type (if known). + * + * @var int + */ + protected $type; + + /** + * The tag of the entry (if known). + * + * @var PelTag + */ + protected $tag; + + /** + * Get the IFD type associated with the exception. + * + * @return int one of {@link PelIfd::IFD0}, {@link PelIfd::IFD1}, + * {@link PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link + * PelIfd::INTEROPERABILITY}. If no type is set, null is returned. + */ + function getIfdType() { + return $this->type; + } + + + /** + * Get the tag associated with the exception. + * + * @return PelTag the tag. If no tag is set, null is returned. + */ + function getTag() { + return $this->tag; + } + +} + + +/** + * Exception indicating that an unexpected format was found. + * + * The documentation for each tag in {@link PelTag} will detail any + * constrains. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + * @subpackage Exception + */ +class PelUnexpectedFormatException extends PelEntryException { + + /** + * Construct a new exception indicating an invalid format. + * + * @param int the type of IFD. + * + * @param PelTag the tag for which the violation was found. + * + * @param PelFormat the format found. + * + * @param PelFormat the expected format. + */ + function __construct($type, $tag, $found, $expected) { + parent::__construct('Unexpected format found for %s tag: PelFormat::%s. ' . + 'Expected PelFormat::%s instead.', + PelTag::getName($type, $tag), + strtoupper(PelFormat::getName($found)), + strtoupper(PelFormat::getName($expected))); + $this->tag = $tag; + $this->type = $type; + } +} + + +/** + * Exception indicating that an unexpected number of components was + * found. + * + * Some tags have strict limits as to the allowed number of + * components, and this exception is thrown if the data violates such + * a constraint. The documentation for each tag in {@link PelTag} + * explains the expected number of components. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + * @subpackage Exception + */ +class PelWrongComponentCountException extends PelEntryException { + + /** + * Construct a new exception indicating a wrong number of + * components. + * + * @param int the type of IFD. + * + * @param PelTag the tag for which the violation was found. + * + * @param int the number of components found. + * + * @param int the expected number of components. + */ + function __construct($type, $tag, $found, $expected) { + parent::__construct('Wrong number of components found for %s tag: %d. ' . + 'Expected %d.', + PelTag::getName($type, $tag), $found, $expected); + $this->tag = $tag; + $this->type = $type; + } +} + + +/** + * Common ancestor class of all {@link PelIfd} entries. + * + * As this class is abstract you cannot instantiate objects from it. + * It only serves as a common ancestor to define the methods common to + * all entries. The most important methods are {@link getValue()} and + * {@link setValue()}, both of which is abstract in this class. The + * descendants will give concrete implementations for them. + * + * If you have some data coming from an image (some raw bytes), then + * the static method {@link newFromData()} is helpful --- it will look + * at the data and give you a proper decendent of {@link PelEntry} + * back. + * + * If you instead want to have an entry for some data which take the + * form of an integer, a string, a byte, or some other PHP type, then + * don't use this class. You should instead create an object of the + * right subclass ({@link PelEntryShort} for short integers, {@link + * PelEntryAscii} for strings, and so on) directly. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +abstract class PelEntry { + + /** + * Type of IFD containing this tag. + * + * This must be one of the constants defined in {@link PelIfd}: + * {@link PelIfd::IFD0} for the main image IFD, {@link PelIfd::IFD1} + * for the thumbnail image IFD, {@link PelIfd::EXIF} for the Exif + * sub-IFD, {@link PelIfd::GPS} for the GPS sub-IFD, or {@link + * PelIfd::INTEROPERABILITY} for the interoperability sub-IFD. + * + * @var int + */ + protected $ifd_type; + + /** + * The bytes representing this entry. + * + * Subclasses must either override {@link getBytes()} or, if + * possible, maintain this property so that it always contains a + * true representation of the entry. + * + * @var string + */ + protected $bytes = ''; + + /** + * The {@link PelTag} of this entry. + * + * @var PelTag + */ + protected $tag; + + /** + * The {@link PelFormat} of this entry. + * + * @var PelFormat + */ + protected $format; + + /** + * The number of components of this entry. + * + * @var int + */ + protected $components; + + + /** + * Return the tag of this entry. + * + * @return PelTag the tag of this entry. + */ + function getTag() { + return $this->tag; + } + + + /** + * Return the type of IFD which holds this entry. + * + * @return int one of the constants defined in {@link PelIfd}: + * {@link PelIfd::IFD0} for the main image IFD, {@link PelIfd::IFD1} + * for the thumbnail image IFD, {@link PelIfd::EXIF} for the Exif + * sub-IFD, {@link PelIfd::GPS} for the GPS sub-IFD, or {@link + * PelIfd::INTEROPERABILITY} for the interoperability sub-IFD. + */ + function getIfdType() { + return $this->ifd_type; + } + + + /** + * Update the IFD type. + * + * @param int must be one of the constants defined in {@link + * PelIfd}: {@link PelIfd::IFD0} for the main image IFD, {@link + * PelIfd::IFD1} for the thumbnail image IFD, {@link PelIfd::EXIF} + * for the Exif sub-IFD, {@link PelIfd::GPS} for the GPS sub-IFD, or + * {@link PelIfd::INTEROPERABILITY} for the interoperability + * sub-IFD. + */ + function setIfdType($type) { + $this->ifd_type = $type; + } + + + /** + * Return the format of this entry. + * + * @return PelFormat the format of this entry. + */ + function getFormat() { + return $this->format; + } + + + /** + * Return the number of components of this entry. + * + * @return int the number of components of this entry. + */ + function getComponents() { + return $this->components; + } + + + /** + * Turn this entry into bytes. + * + * @param PelByteOrder the desired byte order, which must be either + * {@link Convert::LITTLE_ENDIAN} or {@link Convert::BIG_ENDIAN}. + * + * @return string bytes representing this entry. + */ + function getBytes($o) { + return $this->bytes; + } + + + /** + * Get the value of this entry as text. + * + * The value will be returned in a format suitable for presentation, + * e.g., rationals will be returned as 'x/y', ASCII strings will be + * returned as themselves etc. + * + * @param boolean some values can be returned in a long or more + * brief form, and this parameter controls that. + * + * @return string the value as text. + */ + abstract function getText($brief = false); + + + /** + * Get the value of this entry. + * + * The value returned will generally be the same as the one supplied + * to the constructor or with {@link setValue()}. For a formatted + * version of the value, one should use {@link getText()} instead. + * + * @return mixed the unformatted value. + */ + abstract function getValue(); + + + /** + * Set the value of this entry. + * + * The value should be in the same format as for the constructor. + * + * @param mixed the new value. + * + * @abstract + */ + function setValue($value) { + /* This (fake) abstract method is here to make it possible for the + * documentation to refer to PelEntry::setValue(). + * + * It cannot declared abstract in the proper PHP way, for then PHP + * wont allow subclasses to define it with two arguments (which is + * what PelEntryCopyright does). + */ + throw new PelException('setValue() is abstract.'); + } + + + /** + * Turn this entry into a string. + * + * @return string a string representation of this entry. This is + * mostly for debugging. + */ + function __toString() { + $str = Pel::fmt(" Tag: 0x%04X (%s)\n", + $this->tag, PelTag::getName($this->ifd_type, $this->tag)); + $str .= Pel::fmt(" Format : %d (%s)\n", + $this->format, PelFormat::getName($this->format)); + $str .= Pel::fmt(" Components: %d\n", $this->components); + if ($this->getTag() != PelTag::MAKER_NOTE && + $this->getTag() != PelTag::PRINT_IM) + $str .= Pel::fmt(" Value : %s\n", print_r($this->getValue(), true)); + $str .= Pel::fmt(" Text : %s\n", $this->getText()); + return $str; + } +} + diff --git a/modules/autorotate/lib/pel/PelEntryAscii.php b/modules/autorotate/lib/pel/PelEntryAscii.php new file mode 100644 index 0000000..f35f1ac --- /dev/null +++ b/modules/autorotate/lib/pel/PelEntryAscii.php @@ -0,0 +1,561 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006, 2007 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes used to hold ASCII strings. + * + * The classes defined here are to be used for Exif entries holding + * ASCII strings, such as {@link PelTag::MAKE}, {@link + * PelTag::SOFTWARE}, and {@link PelTag::DATE_TIME}. For + * entries holding normal textual ASCII strings the class {@link + * PelEntryAscii} should be used, but for entries holding + * timestamps the class {@link PelEntryTime} would be more + * convenient instead. Copyright information is handled by the {@link + * PelEntryCopyright} class. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelEntry.php'); +/**#@-*/ + + +/** + * Class for holding a plain ASCII string. + * + * This class can hold a single ASCII string, and it will be used as in + * <code> + * $entry = $ifd->getEntry(PelTag::IMAGE_DESCRIPTION); + * print($entry->getValue()); + * $entry->setValue('This is my image. I like it.'); + * </code> + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryAscii extends PelEntry { + + /** + * The string hold by this entry. + * + * This is the string that was given to the {@link __construct + * constructor} or later to {@link setValue}, without any final NULL + * character. + * + * @var string + */ + private $str; + + + /** + * Make a new PelEntry that can hold an ASCII string. + * + * @param int the tag which this entry represents. This should be + * one of the constants defined in {@link PelTag}, e.g., {@link + * PelTag::IMAGE_DESCRIPTION}, {@link PelTag::MODEL}, or any other + * tag with format {@link PelFormat::ASCII}. + * + * @param string the string that this entry will represent. The + * string must obey the same rules as the string argument to {@link + * setValue}, namely that it should be given without any trailing + * NULL character and that it must be plain 7-bit ASCII. + */ + function __construct($tag, $str = '') { + $this->tag = $tag; + $this->format = PelFormat::ASCII; + self::setValue($str); + } + + + /** + * Give the entry a new ASCII value. + * + * This will overwrite the previous value. The value can be + * retrieved later with the {@link getValue} method. + * + * @param string the new value of the entry. This should be given + * without any trailing NULL character. The string must be plain + * 7-bit ASCII, the string should contain no high bytes. + * + * @todo Implement check for high bytes? + */ + function setValue($str) { + $this->components = strlen($str)+1; + $this->str = $str; + $this->bytes = $str . chr(0x00); + } + + + /** + * Return the ASCII string of the entry. + * + * @return string the string held, without any final NULL character. + * The string will be the same as the one given to {@link setValue} + * or to the {@link __construct constructor}. + */ + function getValue() { + return $this->str; + } + + + /** + * Return the ASCII string of the entry. + * + * This methods returns the same as {@link getValue}. + * + * @param boolean not used with ASCII entries. + * + * @return string the string held, without any final NULL character. + * The string will be the same as the one given to {@link setValue} + * or to the {@link __construct constructor}. + */ + function getText($brief = false) { + return $this->str; + } + +} + + +/** + * Class for holding a date and time. + * + * This class can hold a timestamp, and it will be used as + * in this example where the time is advanced by one week: + * <code> + * $entry = $ifd->getEntry(PelTag::DATE_TIME_ORIGINAL); + * $time = $entry->getValue(); + * print('The image was taken on the ' . date('jS', $time)); + * $entry->setValue($time + 7 * 24 * 3600); + * </code> + * + * The example used a standard UNIX timestamp, which is the default + * for this class. + * + * But the Exif format defines dates outside the range of a UNIX + * timestamp (about 1970 to 2038) and so you can also get access to + * the timestamp in two other formats: a simple string or a Julian Day + * Count. Please see the Calendar extension in the PHP Manual for more + * information about the Julian Day Count. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryTime extends PelEntryAscii { + + /** + * Constant denoting a UNIX timestamp. + */ + const UNIX_TIMESTAMP = 1; + /** + * Constant denoting a Exif string. + */ + const EXIF_STRING = 2; + /** + * Constant denoting a Julian Day Count. + */ + const JULIAN_DAY_COUNT = 3; + + /** + * The Julian Day Count of the timestamp held by this entry. + * + * This is an integer counting the number of whole days since + * January 1st, 4713 B.C. The fractional part of the timestamp held + * by this entry is stored in {@link $seconds}. + * + * @var int + */ + private $day_count; + + /** + * The number of seconds into the day of the timestamp held by this + * entry. + * + * The number of whole days is stored in {@link $day_count} and the + * number of seconds left-over is stored here. + * + * @var int + */ + private $seconds; + + + /** + * Make a new entry for holding a timestamp. + * + * @param int the Exif tag which this entry represents. There are + * only three standard tags which hold timestamp, so this should be + * one of the constants {@link PelTag::DATE_TIME}, {@link + * PelTag::DATE_TIME_ORIGINAL}, or {@link + * PelTag::DATE_TIME_DIGITIZED}. + * + * @param int the timestamp held by this entry in the correct form + * as indicated by the third argument. For {@link UNIX_TIMESTAMP} + * this is an integer counting the number of seconds since January + * 1st 1970, for {@link EXIF_STRING} this is a string of the form + * 'YYYY:MM:DD hh:mm:ss', and for {@link JULIAN_DAY_COUNT} this is a + * floating point number where the integer part denotes the day + * count and the fractional part denotes the time of day (0.25 means + * 6:00, 0.75 means 18:00). + * + * @param int the type of the timestamp. This must be one of + * {@link UNIX_TIMESTAMP}, {@link EXIF_STRING}, or + * {@link JULIAN_DAY_COUNT}. + */ + function __construct($tag, $timestamp, $type = self::UNIX_TIMESTAMP) { + parent::__construct($tag); + $this->setValue($timestamp, $type); + } + + + /** + * Return the timestamp of the entry. + * + * The timestamp held by this entry is returned in one of three + * formats: as a standard UNIX timestamp (default), as a fractional + * Julian Day Count, or as a string. + * + * @param int the type of the timestamp. This must be one of + * {@link UNIX_TIMESTAMP}, {@link EXIF_STRING}, or + * {@link JULIAN_DAY_COUNT}. + * + * @return int the timestamp held by this entry in the correct form + * as indicated by the type argument. For {@link UNIX_TIMESTAMP} + * this is an integer counting the number of seconds since January + * 1st 1970, for {@link EXIF_STRING} this is a string of the form + * 'YYYY:MM:DD hh:mm:ss', and for {@link JULIAN_DAY_COUNT} this is a + * floating point number where the integer part denotes the day + * count and the fractional part denotes the time of day (0.25 means + * 6:00, 0.75 means 18:00). + */ + function getValue($type = self::UNIX_TIMESTAMP) { + switch ($type) { + case self::UNIX_TIMESTAMP: + $seconds = $this->convertJdToUnix($this->day_count); + if ($seconds === false) + /* We get false if the Julian Day Count is outside the range + * of a UNIX timestamp. */ + return false; + else + return $seconds + $this->seconds; + + case self::EXIF_STRING: + list($year, $month, $day) = $this->convertJdToGregorian($this->day_count); + $hours = (int)($this->seconds / 3600); + $minutes = (int)($this->seconds % 3600 / 60); + $seconds = $this->seconds % 60; + return sprintf('%04d:%02d:%02d %02d:%02d:%02d', + $year, $month, $day, $hours, $minutes, $seconds); + case self::JULIAN_DAY_COUNT: + return $this->day_count + $this->seconds / 86400; + default: + throw new PelInvalidArgumentException('Expected UNIX_TIMESTAMP (%d), ' . + 'EXIF_STRING (%d), or ' . + 'JULIAN_DAY_COUNT (%d) for $type, '. + 'got %d.', + self::UNIX_TIMESTAMP, + self::EXIF_STRING, + self::JULIAN_DAY_COUNT, + $type); + } + } + + + /** + * Update the timestamp held by this entry. + * + * @param int the timestamp held by this entry in the correct form + * as indicated by the third argument. For {@link UNIX_TIMESTAMP} + * this is an integer counting the number of seconds since January + * 1st 1970, for {@link EXIF_STRING} this is a string of the form + * 'YYYY:MM:DD hh:mm:ss', and for {@link JULIAN_DAY_COUNT} this is a + * floating point number where the integer part denotes the day + * count and the fractional part denotes the time of day (0.25 means + * 6:00, 0.75 means 18:00). + * + * @param int the type of the timestamp. This must be one of + * {@link UNIX_TIMESTAMP}, {@link EXIF_STRING}, or + * {@link JULIAN_DAY_COUNT}. + */ + function setValue($timestamp, $type = self::UNIX_TIMESTAMP) { + #if (empty($timestamp)) + # debug_print_backtrace(); + + switch ($type) { + case self::UNIX_TIMESTAMP: + $this->day_count = $this->convertUnixToJd($timestamp); + $this->seconds = $timestamp % 86400; + break; + + case self::EXIF_STRING: + /* Clean the timestamp: some timestamps are broken other + * separators than ':' and ' '. */ + $d = preg_split('/[^0-9]+/', $timestamp); + $this->day_count = $this->convertGregorianToJd($d[0], $d[1], $d[2]); + $this->seconds = $d[3]*3600 + $d[4]*60 + $d[5]; + break; + + case self::JULIAN_DAY_COUNT: + $this->day_count = (int)floor($timestamp); + $this->seconds = (int)(86400 * ($timestamp - floor($timestamp))); + break; + + default: + throw new PelInvalidArgumentException('Expected UNIX_TIMESTAMP (%d), ' . + 'EXIF_STRING (%d), or ' . + 'JULIAN_DAY_COUNT (%d) for $type, '. + 'got %d.', + self::UNIX_TIMESTAMP, + self::EXIF_STRING, + self::JULIAN_DAY_COUNT, + $type); + } + + /* Now finally update the string which will be used when this is + * turned into bytes. */ + parent::setValue($this->getValue(self::EXIF_STRING)); + } + + + // The following four functions are used for converting back and + // forth between the date formats. They are used in preference to + // the ones from the PHP calendar extension to avoid having to + // fiddle with timezones and to avoid depending on the extension. + // + // See http://www.hermetic.ch/cal_stud/jdn.htm#comp for a reference. + + /** + * Converts a date in year/month/day format to a Julian Day count. + * + * @param int $year the year. + * @param int $month the month, 1 to 12. + * @param int $day the day in the month. + * @return int the Julian Day count. + */ + function convertGregorianToJd($year, $month, $day) { + // Special case mapping 0/0/0 -> 0 + if ($year == 0 || $month == 0 || $day == 0) + return 0; + + $m1412 = ($month <= 2) ? -1 : 0; + return floor(( 1461 * ( $year + 4800 + $m1412 ) ) / 4) + + floor(( 367 * ( $month - 2 - 12 * $m1412 ) ) / 12) - + floor(( 3 * floor( ( $year + 4900 + $m1412 ) / 100 ) ) / 4) + + $day - 32075; + } + + /** + * Converts a Julian Day count to a year/month/day triple. + * + * @param int the Julian Day count. + * @return array an array with three entries: year, month, day. + */ + function convertJdToGregorian($jd) { + // Special case mapping 0 -> 0/0/0 + if ($jd == 0) + return array(0,0,0); + + $l = $jd + 68569; + $n = floor(( 4 * $l ) / 146097); + $l = $l - floor(( 146097 * $n + 3 ) / 4); + $i = floor(( 4000 * ( $l + 1 ) ) / 1461001); + $l = $l - floor(( 1461 * $i ) / 4) + 31; + $j = floor(( 80 * $l ) / 2447); + $d = $l - floor(( 2447 * $j ) / 80); + $l = floor($j / 11); + $m = $j + 2 - ( 12 * $l ); + $y = 100 * ( $n - 49 ) + $i + $l; + return array($y, $m, $d); + } + + /** + * Converts a UNIX timestamp to a Julian Day count. + * + * @param int $timestamp the timestamp. + * @return int the Julian Day count. + */ + function convertUnixToJd($timestamp) { + return (int)(floor($timestamp / 86400) + 2440588); + } + + /** + * Converts a Julian Day count to a UNIX timestamp. + * + * @param int $jd the Julian Day count. + + * @return mixed $timestamp the integer timestamp or false if the + * day count cannot be represented as a UNIX timestamp. + */ + function convertJdToUnix($jd) { + $timestamp = ($jd - 2440588) * 86400; + if ($timestamp != (int)$timestamp) + return false; + else + return $timestamp; + } + +} + + +/** + * Class for holding copyright information. + * + * The Exif standard specifies a certain format for copyright + * information where the one {@link PelTag::COPYRIGHT copyright + * tag} holds both the photographer and editor copyrights, separated + * by a NULL character. + * + * This class is used to manipulate that tag so that the format is + * kept to the standard. A common use would be to add a new copyright + * tag to an image, since most cameras do not add this tag themselves. + * This would be done like this: + * + * <code> + * $entry = new PelEntryCopyright('Copyright, Martin Geisler, 2004'); + * $ifd0->addEntry($entry); + * </code> + * + * Here we only set the photographer copyright, use the optional + * second argument to specify the editor copyright. If there is only + * an editor copyright, then let the first argument be the empty + * string. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryCopyright extends PelEntryAscii { + + /** + * The photographer copyright. + * + * @var string + */ + private $photographer; + + /** + * The editor copyright. + * + * @var string + */ + private $editor; + + + /** + * Make a new entry for holding copyright information. + * + * @param string the photographer copyright. Use the empty string + * if there is no photographer copyright. + * + * @param string the editor copyright. Use the empty string if + * there is no editor copyright. + */ + function __construct($photographer = '', $editor = '') { + parent::__construct(PelTag::COPYRIGHT); + $this->setValue($photographer, $editor); + } + + + /** + * Update the copyright information. + * + * @param string the photographer copyright. Use the empty string + * if there is no photographer copyright. + * + * @param string the editor copyright. Use the empty string if + * there is no editor copyright. + */ + function setValue($photographer = '', $editor = '') { + $this->photographer = $photographer; + $this->editor = $editor; + + if ($photographer == '' && $editor != '') + $photographer = ' '; + + if ($editor == '') + parent::setValue($photographer); + else + parent::setValue($photographer . chr(0x00) . $editor); + } + + + /** + * Retrive the copyright information. + * + * The strings returned will be the same as the one used previously + * with either {@link __construct the constructor} or with {@link + * setValue}. + * + * @return array an array with two strings, the photographer and + * editor copyrights. The two fields will be returned in that + * order, so that the first array index will be the photographer + * copyright, and the second will be the editor copyright. + */ + function getValue() { + return array($this->photographer, $this->editor); + } + + + /** + * Return a text string with the copyright information. + * + * The photographer and editor copyright fields will be returned + * with a '-' in between if both copyright fields are present, + * otherwise only one of them will be returned. + * + * @param boolean if false, then the strings '(Photographer)' and + * '(Editor)' will be appended to the photographer and editor + * copyright fields (if present), otherwise the fields will be + * returned as is. + * + * @return string the copyright information in a string. + */ + function getText($brief = false) { + if ($brief) { + $p = ''; + $e = ''; + } else { + $p = ' ' . Pel::tra('(Photographer)'); + $e = ' ' . Pel::tra('(Editor)'); + } + + if ($this->photographer != '' && $this->editor != '') + return $this->photographer . $p . ' - ' . $this->editor . $e; + + if ($this->photographer != '') + return $this->photographer . $p; + + if ($this->editor != '') + return $this->editor . $e; + + return ''; + } +} + diff --git a/modules/autorotate/lib/pel/PelEntryByte.php b/modules/autorotate/lib/pel/PelEntryByte.php new file mode 100644 index 0000000..5685b5a --- /dev/null +++ b/modules/autorotate/lib/pel/PelEntryByte.php @@ -0,0 +1,280 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006, 2007 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes used to hold bytes, both signed and unsigned. The {@link + * PelEntryWindowsString} class is used to manipulate strings in the + * format Windows XP needs. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelEntryNumber.php'); +/**#@-*/ + + +/** + * Class for holding unsigned bytes. + * + * This class can hold bytes, either just a single byte or an array of + * bytes. The class will be used to manipulate any of the Exif tags + * which has format {@link PelFormat::BYTE}. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryByte extends PelEntryNumber { + + /** + * Make a new entry that can hold an unsigned byte. + * + * The method accept several integer arguments. The {@link + * getValue} method will always return an array except for when a + * single integer argument is given here. + * + * @param PelTag the tag which this entry represents. This + * should be one of the constants defined in {@link PelTag} + * which has format {@link PelFormat::BYTE}. + * + * @param int $value... the byte(s) that this entry will represent. + * The argument passed must obey the same rules as the argument to + * {@link setValue}, namely that it should be within range of an + * unsigned byte, that is between 0 and 255 (inclusive). If not, + * then a {@link PelOverflowException} will be thrown. + */ + function __construct($tag /* $value... */) { + $this->tag = $tag; + $this->min = 0; + $this->max = 255; + $this->format = PelFormat::BYTE; + + $value = func_get_args(); + array_shift($value); + $this->setValueArray($value); + } + + + /** + * Convert a number into bytes. + * + * @param int the number that should be converted. + * + * @param PelByteOrder one of {@link PelConvert::LITTLE_ENDIAN} and + * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order. + * + * @return string bytes representing the number given. + */ + function numberToBytes($number, $order) { + return chr($number); + } + +} + + +/** + * Class for holding signed bytes. + * + * This class can hold bytes, either just a single byte or an array of + * bytes. The class will be used to manipulate any of the Exif tags + * which has format {@link PelFormat::BYTE}. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntrySByte extends PelEntryNumber { + + /** + * Make a new entry that can hold a signed byte. + * + * The method accept several integer arguments. The {@link getValue} + * method will always return an array except for when a single + * integer argument is given here. + * + * @param PelTag the tag which this entry represents. This + * should be one of the constants defined in {@link PelTag} + * which has format {@link PelFormat::BYTE}. + * + * @param int $value... the byte(s) that this entry will represent. + * The argument passed must obey the same rules as the argument to + * {@link setValue}, namely that it should be within range of a + * signed byte, that is between -128 and 127 (inclusive). If not, + * then a {@link PelOverflowException} will be thrown. + */ + function __construct($tag /* $value... */) { + $this->tag = $tag; + $this->min = -128; + $this->max = 127; + $this->format = PelFormat::SBYTE; + + $value = func_get_args(); + array_shift($value); + $this->setValueArray($value); + } + + + /** + * Convert a number into bytes. + * + * @param int the number that should be converted. + * + * @param PelByteOrder one of {@link PelConvert::LITTLE_ENDIAN} and + * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order. + * + * @return string bytes representing the number given. + */ + function numberToBytes($number, $order) { + return chr($number); + } + +} + + +/** + * Class used to manipulate strings in the format Windows XP uses. + * + * When examining the file properties of an image in Windows XP one + * can fill in title, comment, author, keyword, and subject fields. + * Filling those fields and pressing OK will result in the data being + * written into the Exif data in the image. + * + * The data is written in a non-standard format and can thus not be + * loaded directly --- this class is needed to translate it into + * normal strings. + * + * It is important that entries from this class are only created with + * the {@link PelTag::XP_TITLE}, {@link PelTag::XP_COMMENT}, {@link + * PelTag::XP_AUTHOR}, {@link PelTag::XP_KEYWORD}, and {@link + * PelTag::XP_SUBJECT} tags. If another tag is used the data will no + * longer be correctly decoded when reloaded with PEL. (The data will + * be loaded as an {@link PelEntryByte} entry, which isn't as useful.) + * + * This class is to be used as in + * <code> + * $title = $ifd->getEntry(PelTag::XP_TITLE); + * print($title->getValue()); + * $title->setValue('My favorite cat'); + * </code> + * or if no entry is present one can add a new one with + * <code> + * $title = new PelEntryWindowsString(PelTag::XP_TITLE, 'A cute dog.'); + * $ifd->addEntry($title); + * </code> + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryWindowsString extends PelEntry { + + /** + * The string hold by this entry. + * + * This is the string that was given to the {@link __construct + * constructor} or later to {@link setValue}, without any extra NULL + * characters or any such nonsense. + * + * @var string + */ + private $str; + + + /** + * Make a new PelEntry that can hold a Windows XP specific string. + * + * @param int the tag which this entry represents. This should be + * one of {@link PelTag::XP_TITLE}, {@link PelTag::XP_COMMENT}, + * {@link PelTag::XP_AUTHOR}, {@link PelTag::XP_KEYWORD}, and {@link + * PelTag::XP_SUBJECT} tags. If another tag is used, then this + * entry will be incorrectly reloaded as a {@link PelEntryByte}. + * + * @param string the string that this entry will represent. It will + * be passed to {@link setValue} and thus has to obey its + * requirements. + */ + function __construct($tag, $str = '') { + $this->tag = $tag; + $this->format = PelFormat::BYTE; + $this->setValue($str); + } + + + /** + * Give the entry a new value. + * + * This will overwrite the previous value. The value can be + * retrieved later with the {@link getValue} method. + * + * @param string the new value of the entry. This should be use the + * Latin-1 encoding and be given without any extra NULL characters. + */ + function setValue($str) { + $l = strlen($str); + + $this->components = 2 * ($l + 1); + $this->str = $str; + $this->bytes = ''; + for ($i = 0; $i < $l; $i++) + $this->bytes .= $str{$i} . chr(0x00); + + $this->bytes .= chr(0x00) . chr(0x00); + } + + + /** + * Return the string of the entry. + * + * @return string the string held, without any extra NULL + * characters. The string will be the same as the one given to + * {@link setValue} or to the {@link __construct constructor}. + */ + function getValue() { + return $this->str; + } + + + /** + * Return the string of the entry. + * + * This methods returns the same as {@link getValue}. + * + * @param boolean not used. + * + * @return string the string held, without any extra NULL + * characters. The string will be the same as the one given to + * {@link setValue} or to the {@link __construct constructor}. + */ + function getText($brief = false) { + return $this->str; + } + +} + diff --git a/modules/autorotate/lib/pel/PelEntryLong.php b/modules/autorotate/lib/pel/PelEntryLong.php new file mode 100644 index 0000000..aa8cd51 --- /dev/null +++ b/modules/autorotate/lib/pel/PelEntryLong.php @@ -0,0 +1,181 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes used to hold longs, both signed and unsigned. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelEntryNumber.php'); +/**#@-*/ + + +/** + * Class for holding unsigned longs. + * + * This class can hold longs, either just a single long or an array of + * longs. The class will be used to manipulate any of the Exif tags + * which can have format {@link PelFormat::LONG} like in this + * example: + * <code> + * $w = $ifd->getEntry(PelTag::EXIF_IMAGE_WIDTH); + * $w->setValue($w->getValue() / 2); + * $h = $ifd->getEntry(PelTag::EXIF_IMAGE_HEIGHT); + * $h->setValue($h->getValue() / 2); + * </code> + * Here the width and height is updated to 50% of their original + * values. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryLong extends PelEntryNumber { + + /** + * Make a new entry that can hold an unsigned long. + * + * The method accept its arguments in two forms: several integer + * arguments or a single array argument. The {@link getValue} + * method will always return an array except for when a single + * integer argument is given here, or when an array with just a + * single integer is given. + * + * This means that one can conveniently use objects like this: + * <code> + * $a = new PelEntryLong(PelTag::EXIF_IMAGE_WIDTH, 123456); + * $b = $a->getValue() - 654321; + * </code> + * where the call to {@link getValue} will return an integer instead + * of an array with one integer element, which would then have to be + * extracted. + * + * @param PelTag the tag which this entry represents. This + * should be one of the constants defined in {@link PelTag}, + * e.g., {@link PelTag::IMAGE_WIDTH}, or any other tag which can + * have format {@link PelFormat::LONG}. + * + * @param int $value... the long(s) that this entry will + * represent or an array of longs. The argument passed must obey + * the same rules as the argument to {@link setValue}, namely that + * it should be within range of an unsigned long (32 bit), that is + * between 0 and 4294967295 (inclusive). If not, then a {@link + * PelExifOverflowException} will be thrown. + */ + function __construct($tag /* $value... */) { + $this->tag = $tag; + $this->min = 0; + $this->max = 4294967295; + $this->format = PelFormat::LONG; + + $value = func_get_args(); + array_shift($value); + $this->setValueArray($value); + } + + + /** + * Convert a number into bytes. + * + * @param int the number that should be converted. + * + * @param PelByteOrder one of {@link PelConvert::LITTLE_ENDIAN} and + * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order. + * + * @return string bytes representing the number given. + */ + function numberToBytes($number, $order) { + return PelConvert::longToBytes($number, $order); + } +} + + +/** + * Class for holding signed longs. + * + * This class can hold longs, either just a single long or an array of + * longs. The class will be used to manipulate any of the Exif tags + * which can have format {@link PelFormat::SLONG}. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntrySLong extends PelEntryNumber { + + /** + * Make a new entry that can hold a signed long. + * + * The method accept its arguments in two forms: several integer + * arguments or a single array argument. The {@link getValue} + * method will always return an array except for when a single + * integer argument is given here, or when an array with just a + * single integer is given. + * + * @param PelTag the tag which this entry represents. This + * should be one of the constants defined in {@link PelTag} + * which have format {@link PelFormat::SLONG}. + * + * @param int $value... the long(s) that this entry will represent + * or an array of longs. The argument passed must obey the same + * rules as the argument to {@link setValue}, namely that it should + * be within range of a signed long (32 bit), that is between + * -2147483648 and 2147483647 (inclusive). If not, then a {@link + * PelOverflowException} will be thrown. + */ + function __construct($tag /* $value... */) { + $this->tag = $tag; + $this->min = -2147483648; + $this->max = 2147483647; + $this->format = PelFormat::SLONG; + + $value = func_get_args(); + array_shift($value); + $this->setValueArray($value); + } + + + /** + * Convert a number into bytes. + * + * @param int the number that should be converted. + * + * @param PelByteOrder one of {@link PelConvert::LITTLE_ENDIAN} and + * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order. + * + * @return string bytes representing the number given. + */ + function numberToBytes($number, $order) { + return PelConvert::sLongToBytes($number, $order); + } +} + diff --git a/modules/autorotate/lib/pel/PelEntryNumber.php b/modules/autorotate/lib/pel/PelEntryNumber.php new file mode 100644 index 0000000..0e94992 --- /dev/null +++ b/modules/autorotate/lib/pel/PelEntryNumber.php @@ -0,0 +1,309 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Abstract class for numbers. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelException.php'); +require_once('PelEntry.php'); +/**#@-*/ + + +/** + * Exception cast when numbers overflow. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + * @subpackage Exception + */ +class PelOverflowException extends PelException { + + /** + * Construct a new overflow exception. + * + * @param int the value that is out of range. + * + * @param int the minimum allowed value. + * + * @param int the maximum allowed value. + */ + function __construct($v, $min, $max) { + parent::__construct('Value %.0f out of range [%.0f, %.0f]', + $v, $min, $max); + } +} + + +/** + * Class for holding numbers. + * + * This class can hold numbers, with range checks. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +abstract class PelEntryNumber extends PelEntry { + + /** + * The value held by this entry. + * + * @var array + */ + protected $value = array(); + + /** + * The minimum allowed value. + * + * Any attempt to change the value below this variable will result + * in a {@link PelOverflowException} being thrown. + * + * @var int + */ + protected $min; + + /** + * The maximum allowed value. + * + * Any attempt to change the value over this variable will result in + * a {@link PelOverflowException} being thrown. + * + * @var int + */ + protected $max; + + /** + * The dimension of the number held. + * + * Normal numbers have a dimension of one, pairs have a dimension of + * two, etc. + * + * @var int + */ + protected $dimension = 1; + + + /** + * Change the value. + * + * This method can change both the number of components and the + * value of the components. Range checks will be made on the new + * value, and a {@link PelOverflowException} will be thrown if the + * value is found to be outside the legal range. + * + * The method accept several number arguments. The {@link getValue} + * method will always return an array except for when a single + * number is given here. + * + * @param int|array $value... the new value(s). This can be zero or + * more numbers, that is, either integers or arrays. The input will + * be checked to ensure that the numbers are within the valid range. + * If not, then a {@link PelOverflowException} will be thrown. + * + * @see getValue + */ + function setValue(/* $value... */) { + $value = func_get_args(); + $this->setValueArray($value); + } + + + /** + * Change the value. + * + * This method can change both the number of components and the + * value of the components. Range checks will be made on the new + * value, and a {@link PelOverflowException} will be thrown if the + * value is found to be outside the legal range. + * + * @param array the new values. The array must contain the new + * numbers. + * + * @see getValue + */ + function setValueArray($value) { + foreach ($value as $v) + $this->validateNumber($v); + + $this->components = count($value); + $this->value = $value; + } + + + /** + * Return the numeric value held. + * + * @return int|array this will either be a single number if there is + * only one component, or an array of numbers otherwise. + */ + function getValue() { + if ($this->components == 1) + return $this->value[0]; + else + return $this->value; + } + + + /** + * Validate a number. + * + * This method will check that the number given is within the range + * given my {@link getMin()} and {@link getMax()}, inclusive. If + * not, then a {@link PelOverflowException} is thrown. + * + * @param int|array the number in question. + * + * @return void nothing, but will throw a {@link + * PelOverflowException} if the number is found to be outside the + * legal range and {@link Pel::$strict} is true. + */ + function validateNumber($n) { + if ($this->dimension == 1) { + if ($n < $this->min || $n > $this->max) + Pel::maybeThrow(new PelOverflowException($n, + $this->min, + $this->max)); + } else { + for ($i = 0; $i < $this->dimension; $i++) + if ($n[$i] < $this->min || $n[$i] > $this->max) + Pel::maybeThrow(new PelOverflowException($n[$i], + $this->min, + $this->max)); + } + } + + + /** + * Add a number. + * + * This appends a number to the numbers already held by this entry, + * thereby increasing the number of components by one. + * + * @param int|array the number to be added. + */ + function addNumber($n) { + $this->validateNumber($n); + $this->value[] = $n; + $this->components++; + } + + + /** + * Convert a number into bytes. + * + * The concrete subclasses will have to implement this method so + * that the numbers represented can be turned into bytes. + * + * The method will be called once for each number held by the entry. + * + * @param int the number that should be converted. + * + * @param PelByteOrder one of {@link PelConvert::LITTLE_ENDIAN} and + * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order. + * + * @return string bytes representing the number given. + */ + abstract function numberToBytes($number, $order); + + + /** + * Turn this entry into bytes. + * + * @param PelByteOrder the desired byte order, which must be either + * {@link PelConvert::LITTLE_ENDIAN} or {@link + * PelConvert::BIG_ENDIAN}. + * + * @return string bytes representing this entry. + */ + function getBytes($o) { + $bytes = ''; + for ($i = 0; $i < $this->components; $i++) { + if ($this->dimension == 1) { + $bytes .= $this->numberToBytes($this->value[$i], $o); + } else { + for ($j = 0; $j < $this->dimension; $j++) { + $bytes .= $this->numberToBytes($this->value[$i][$j], $o); + } + } + } + return $bytes; + } + + + /** + * Format a number. + * + * This method is called by {@link getText} to format numbers. + * Subclasses should override this method if they need more + * sophisticated behavior than the default, which is to just return + * the number as is. + * + * @param int the number which will be formatted. + * + * @param boolean it could be that there is both a verbose and a + * brief formatting available, and this argument controls that. + * + * @return string the number formatted as a string suitable for + * display. + */ + function formatNumber($number, $brief = false) { + return $number; + } + + + /** + * Get the numeric value of this entry as text. + * + * @param boolean use brief output? The numbers will be separated + * by a single space if brief output is requested, otherwise a space + * and a comma will be used. + * + * @return string the numbers(s) held by this entry. + */ + function getText($brief = false) { + if ($this->components == 0) + return ''; + + $str = $this->formatNumber($this->value[0]); + for ($i = 1; $i < $this->components; $i++) { + $str .= ($brief ? ' ' : ', '); + $str .= $this->formatNumber($this->value[$i]); + } + + return $str; + } + +} + diff --git a/modules/autorotate/lib/pel/PelEntryRational.php b/modules/autorotate/lib/pel/PelEntryRational.php new file mode 100644 index 0000000..46a5ccf --- /dev/null +++ b/modules/autorotate/lib/pel/PelEntryRational.php @@ -0,0 +1,290 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes used to manipulate rational numbers. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelEntryLong.php'); +/**#@-*/ + + +/** + * Class for holding unsigned rational numbers. + * + * This class can hold rational numbers, consisting of a numerator and + * denominator both of which are of type unsigned long. Each rational + * is represented by an array with just two entries: the numerator and + * the denominator, in that order. + * + * The class can hold either just a single rational or an array of + * rationals. The class will be used to manipulate any of the Exif + * tags which can have format {@link PelFormat::RATIONAL} like in this + * example: + * + * <code> + * $resolution = $ifd->getEntry(PelTag::X_RESOLUTION); + * $resolution->setValue(array(1, 300)); + * </code> + * + * Here the x-resolution is adjusted to 1/300, which will be 300 DPI, + * unless the {@link PelTag::RESOLUTION_UNIT resolution unit} is set + * to something different than 2 which means inches. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryRational extends PelEntryLong { + + /** + * Make a new entry that can hold an unsigned rational. + * + * @param PelTag the tag which this entry represents. This should + * be one of the constants defined in {@link PelTag}, e.g., {@link + * PelTag::X_RESOLUTION}, or any other tag which can have format + * {@link PelFormat::RATIONAL}. + * + * @param array $value... the rational(s) that this entry will + * represent. The arguments passed must obey the same rules as the + * argument to {@link setValue}, namely that each argument should be + * an array with two entries, both of which must be within range of + * an unsigned long (32 bit), that is between 0 and 4294967295 + * (inclusive). If not, then a {@link PelOverflowException} will be + * thrown. + */ + function __construct($tag /* $value... */) { + $this->tag = $tag; + $this->format = PelFormat::RATIONAL; + $this->dimension = 2; + $this->min = 0; + $this->max = 4294967295; + + $value = func_get_args(); + array_shift($value); + $this->setValueArray($value); + } + + + /** + * Format a rational number. + * + * The rational will be returned as a string with a slash '/' + * between the numerator and denominator. + * + * @param array the rational which will be formatted. + * + * @param boolean not used. + * + * @return string the rational formatted as a string suitable for + * display. + */ + function formatNumber($number, $brief = false) { + return $number[0] . '/' . $number[1]; + } + + + /** + * Get the value of an entry as text. + * + * The value will be returned in a format suitable for presentation, + * e.g., rationals will be returned as 'x/y', ASCII strings will be + * returned as themselves etc. + * + * @param boolean some values can be returned in a long or more + * brief form, and this parameter controls that. + * + * @return string the value as text. + */ + function getText($brief = false) { + if (isset($this->value[0])) + $v = $this->value[0]; + + switch ($this->tag) { + case PelTag::FNUMBER: + //CC (e->components, 1, v); + return Pel::fmt('f/%.01f', $v[0]/$v[1]); + + case PelTag::APERTURE_VALUE: + //CC (e->components, 1, v); + //if (!v_rat.denominator) return (NULL); + return Pel::fmt('f/%.01f', pow(2, $v[0]/$v[1]/2)); + + case PelTag::FOCAL_LENGTH: + //CC (e->components, 1, v); + //if (!v_rat.denominator) return (NULL); + return Pel::fmt('%.1f mm', $v[0]/$v[1]); + + case PelTag::SUBJECT_DISTANCE: + //CC (e->components, 1, v); + //if (!v_rat.denominator) return (NULL); + return Pel::fmt('%.1f m', $v[0]/$v[1]); + + case PelTag::EXPOSURE_TIME: + //CC (e->components, 1, v); + //if (!v_rat.denominator) return (NULL); + if ($v[0]/$v[1] < 1) + return Pel::fmt('1/%d sec.', $v[1]/$v[0]); + else + return Pel::fmt('%d sec.', $v[0]/$v[1]); + + case PelTag::GPS_LATITUDE: + case PelTag::GPS_LONGITUDE: + $degrees = $this->value[0][0]/$this->value[0][1]; + $minutes = $this->value[1][0]/$this->value[1][1]; + $seconds = $this->value[2][0]/$this->value[2][1]; + + return sprintf('%s° %s\' %s" (%.2f°)', + $degrees, $minutes, $seconds, + $degrees + $minutes/60 + $seconds/3600); + + default: + return parent::getText($brief); + } + } +} + + +/** + * Class for holding signed rational numbers. + * + * This class can hold rational numbers, consisting of a numerator and + * denominator both of which are of type unsigned long. Each rational + * is represented by an array with just two entries: the numerator and + * the denominator, in that order. + * + * The class can hold either just a single rational or an array of + * rationals. The class will be used to manipulate any of the Exif + * tags which can have format {@link PelFormat::SRATIONAL}. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntrySRational extends PelEntrySLong { + + /** + * Make a new entry that can hold a signed rational. + * + * @param PelTag the tag which this entry represents. This should + * be one of the constants defined in {@link PelTag}, e.g., {@link + * PelTag::SHUTTER_SPEED_VALUE}, or any other tag which can have + * format {@link PelFormat::SRATIONAL}. + * + * @param array $value... the rational(s) that this entry will + * represent. The arguments passed must obey the same rules as the + * argument to {@link setValue}, namely that each argument should be + * an array with two entries, both of which must be within range of + * a signed long (32 bit), that is between -2147483648 and + * 2147483647 (inclusive). If not, then a {@link + * PelOverflowException} will be thrown. + */ + function __construct($tag /* $value... */) { + $this->tag = $tag; + $this->format = PelFormat::SRATIONAL; + $this->dimension = 2; + $this->min = -2147483648; + $this->max = 2147483647; + + $value = func_get_args(); + array_shift($value); + $this->setValueArray($value); + } + + + /** + * Format a rational number. + * + * The rational will be returned as a string with a slash '/' + * between the numerator and denominator. Care is taken to display + * '-1/2' instead of the ugly but mathematically equivalent '1/-2'. + * + * @param array the rational which will be formatted. + * + * @param boolean not used. + * + * @return string the rational formatted as a string suitable for + * display. + */ + function formatNumber($number, $brief = false) { + if ($number[1] < 0) + /* Turn output like 1/-2 into -1/2. */ + return (-$number[0]) . '/' . (-$number[1]); + else + return $number[0] . '/' . $number[1]; + } + + + /** + * Get the value of an entry as text. + * + * The value will be returned in a format suitable for presentation, + * e.g., rationals will be returned as 'x/y', ASCII strings will be + * returned as themselves etc. + * + * @param boolean some values can be returned in a long or more + * brief form, and this parameter controls that. + * + * @return string the value as text. + */ + function getText($brief = false) { + if (isset($this->value[0])) + $v = $this->value[0]; + + switch ($this->tag) { + case PelTag::SHUTTER_SPEED_VALUE: + //CC (e->components, 1, v); + //if (!v_srat.denominator) return (NULL); + return Pel::fmt('%.0f/%.0f sec. (APEX: %d)', + $v[0], $v[1], pow(sqrt(2), $v[0]/$v[1])); + + case PelTag::BRIGHTNESS_VALUE: + //CC (e->components, 1, v); + // + // TODO: figure out the APEX thing, or remove this so that it is + // handled by the default clause at the bottom. + return sprintf('%d/%d', $v[0], $v[1]); + //FIXME: How do I calculate the APEX value? + + case PelTag::EXPOSURE_BIAS_VALUE: + //CC (e->components, 1, v); + //if (!v_srat.denominator) return (NULL); + return sprintf('%s%.01f', $v[0]*$v[1] > 0 ? '+' : '', $v[0]/$v[1]); + + default: + return parent::getText($brief); + } + } + +} + diff --git a/modules/autorotate/lib/pel/PelEntryShort.php b/modules/autorotate/lib/pel/PelEntryShort.php new file mode 100644 index 0000000..319f3fd --- /dev/null +++ b/modules/autorotate/lib/pel/PelEntryShort.php @@ -0,0 +1,598 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes used to hold shorts, both signed and unsigned. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelEntryNumber.php'); +require_once('PelConvert.php'); +require_once('Pel.php'); +/**#@-*/ + + +/** + * Class for holding signed shorts. + * + * This class can hold shorts, either just a single short or an array + * of shorts. The class will be used to manipulate any of the Exif + * tags which has format {@link PelFormat::SHORT} like in this + * example: + * + * <code> + * $w = $ifd->getEntry(PelTag::EXIF_IMAGE_WIDTH); + * $w->setValue($w->getValue() / 2); + * $h = $ifd->getEntry(PelTag::EXIF_IMAGE_HEIGHT); + * $h->setValue($h->getValue() / 2); + * </code> + * + * Here the width and height is updated to 50% of their original + * values. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryShort extends PelEntryNumber { + + /** + * Make a new entry that can hold an unsigned short. + * + * The method accept several integer arguments. The {@link + * getValue} method will always return an array except for when a + * single integer argument is given here. + * + * This means that one can conveniently use objects like this: + * <code> + * $a = new PelEntryShort(PelTag::EXIF_IMAGE_HEIGHT, 42); + * $b = $a->getValue() + 314; + * </code> + * where the call to {@link getValue} will return an integer + * instead of an array with one integer element, which would then + * have to be extracted. + * + * @param PelTag the tag which this entry represents. This should be + * one of the constants defined in {@link PelTag}, e.g., {@link + * PelTag::IMAGE_WIDTH}, {@link PelTag::ISO_SPEED_RATINGS}, + * or any other tag with format {@link PelFormat::SHORT}. + * + * @param int $value... the short(s) that this entry will + * represent. The argument passed must obey the same rules as the + * argument to {@link setValue}, namely that it should be within + * range of an unsigned short, that is between 0 and 65535 + * (inclusive). If not, then a {@link PelOverFlowException} will be + * thrown. + */ + function __construct($tag /* $value... */) { + $this->tag = $tag; + $this->min = 0; + $this->max = 65535; + $this->format = PelFormat::SHORT; + + $value = func_get_args(); + array_shift($value); + $this->setValueArray($value); + } + + + /** + * Convert a number into bytes. + * + * @param int the number that should be converted. + * + * @param PelByteOrder one of {@link PelConvert::LITTLE_ENDIAN} and + * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order. + * + * @return string bytes representing the number given. + */ + function numberToBytes($number, $order) { + return PelConvert::shortToBytes($number, $order); + } + + + /** + * Get the value of an entry as text. + * + * The value will be returned in a format suitable for presentation, + * e.g., instead of returning '2' for a {@link + * PelTag::METERING_MODE} tag, 'Center-Weighted Average' is + * returned. + * + * @param boolean some values can be returned in a long or more + * brief form, and this parameter controls that. + * + * @return string the value as text. + */ + function getText($brief = false) { + switch ($this->tag) { + case PelTag::METERING_MODE: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Unknown'); + case 1: + return Pel::tra('Average'); + case 2: + return Pel::tra('Center-Weighted Average'); + case 3: + return Pel::tra('Spot'); + case 4: + return Pel::tra('Multi Spot'); + case 5: + return Pel::tra('Pattern'); + case 6: + return Pel::tra('Partial'); + case 255: + return Pel::tra('Other'); + default: + return $this->value[0]; + } + + case PelTag::COMPRESSION: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 1: + return Pel::tra('Uncompressed'); + case 6: + return Pel::tra('JPEG compression'); + default: + return $this->value[0]; + + } + + case PelTag::PLANAR_CONFIGURATION: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 1: + return Pel::tra('chunky format'); + case 2: + return Pel::tra('planar format'); + default: + return $this->value[0]; + } + + case PelTag::SENSING_METHOD: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 1: + return Pel::tra('Not defined'); + case 2: + return Pel::tra('One-chip color area sensor'); + case 3: + return Pel::tra('Two-chip color area sensor'); + case 4: + return Pel::tra('Three-chip color area sensor'); + case 5: + return Pel::tra('Color sequential area sensor'); + case 7: + return Pel::tra('Trilinear sensor'); + case 8: + return Pel::tra('Color sequential linear sensor'); + default: + return $this->value[0]; + } + + case PelTag::LIGHT_SOURCE: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Unknown'); + case 1: + return Pel::tra('Daylight'); + case 2: + return Pel::tra('Fluorescent'); + case 3: + return Pel::tra('Tungsten (incandescent light)'); + case 4: + return Pel::tra('Flash'); + case 9: + return Pel::tra('Fine weather'); + case 10: + return Pel::tra('Cloudy weather'); + case 11: + return Pel::tra('Shade'); + case 12: + return Pel::tra('Daylight fluorescent'); + case 13: + return Pel::tra('Day white fluorescent'); + case 14: + return Pel::tra('Cool white fluorescent'); + case 15: + return Pel::tra('White fluorescent'); + case 17: + return Pel::tra('Standard light A'); + case 18: + return Pel::tra('Standard light B'); + case 19: + return Pel::tra('Standard light C'); + case 20: + return Pel::tra('D55'); + case 21: + return Pel::tra('D65'); + case 22: + return Pel::tra('D75'); + case 24: + return Pel::tra('ISO studio tungsten'); + case 255: + return Pel::tra('Other'); + default: + return $this->value[0]; + } + + case PelTag::FOCAL_PLANE_RESOLUTION_UNIT: + case PelTag::RESOLUTION_UNIT: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 2: + return Pel::tra('Inch'); + case 3: + return Pel::tra('Centimeter'); + default: + return $this->value[0]; + } + + case PelTag::EXPOSURE_PROGRAM: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Not defined'); + case 1: + return Pel::tra('Manual'); + case 2: + return Pel::tra('Normal program'); + case 3: + return Pel::tra('Aperture priority'); + case 4: + return Pel::tra('Shutter priority'); + case 5: + return Pel::tra('Creative program (biased toward depth of field)'); + case 6: + return Pel::tra('Action program (biased toward fast shutter speed)'); + case 7: + return Pel::tra('Portrait mode (for closeup photos with the background out of focus'); + case 8: + return Pel::tra('Landscape mode (for landscape photos with the background in focus'); + default: + return $this->value[0]; + } + + case PelTag::ORIENTATION: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 1: + return Pel::tra('top - left'); + case 2: + return Pel::tra('top - right'); + case 3: + return Pel::tra('bottom - right'); + case 4: + return Pel::tra('bottom - left'); + case 5: + return Pel::tra('left - top'); + case 6: + return Pel::tra('right - top'); + case 7: + return Pel::tra('right - bottom'); + case 8: + return Pel::tra('left - bottom'); + default: + return $this->value[0]; + } + + case PelTag::YCBCR_POSITIONING: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 1: + return Pel::tra('centered'); + case 2: + return Pel::tra('co-sited'); + default: + return $this->value[0]; + } + + case PelTag::YCBCR_SUB_SAMPLING: + //CC (e->components, 2, v); + if ($this->value[0] == 2 && $this->value[1] == 1) + return 'YCbCr4:2:2'; + if ($this->value[0] == 2 && $this->value[1] == 2) + return 'YCbCr4:2:0'; + + return $this->value[0] . ', ' . $this->value[1]; + + case PelTag::PHOTOMETRIC_INTERPRETATION: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 2: + return 'RGB'; + case 6: + return 'YCbCr'; + default: + return $this->value[0]; + } + + case PelTag::COLOR_SPACE: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 1: + return 'sRGB'; + case 2: + return 'Adobe RGB'; + case 0xffff: + return Pel::tra('Uncalibrated'); + default: + return $this->value[0]; + } + + case PelTag::FLASH: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0x0000: + return Pel::tra('Flash did not fire.'); + case 0x0001: + return Pel::tra('Flash fired.'); + case 0x0005: + return Pel::tra('Strobe return light not detected.'); + case 0x0007: + return Pel::tra('Strobe return light detected.'); + case 0x0009: + return Pel::tra('Flash fired, compulsory flash mode.'); + case 0x000d: + return Pel::tra('Flash fired, compulsory flash mode, return light not detected.'); + case 0x000f: + return Pel::tra('Flash fired, compulsory flash mode, return light detected.'); + case 0x0010: + return Pel::tra('Flash did not fire, compulsory flash mode.'); + case 0x0018: + return Pel::tra('Flash did not fire, auto mode.'); + case 0x0019: + return Pel::tra('Flash fired, auto mode.'); + case 0x001d: + return Pel::tra('Flash fired, auto mode, return light not detected.'); + case 0x001f: + return Pel::tra('Flash fired, auto mode, return light detected.'); + case 0x0020: + return Pel::tra('No flash function.'); + case 0x0041: + return Pel::tra('Flash fired, red-eye reduction mode.'); + case 0x0045: + return Pel::tra('Flash fired, red-eye reduction mode, return light not detected.'); + case 0x0047: + return Pel::tra('Flash fired, red-eye reduction mode, return light detected.'); + case 0x0049: + return Pel::tra('Flash fired, compulsory flash mode, red-eye reduction mode.'); + case 0x004d: + return Pel::tra('Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected.'); + case 0x004f: + return Pel::tra('Flash fired, compulsory flash mode, red-eye reduction mode, return light detected.'); + case 0x0058: + return Pel::tra('Flash did not fire, auto mode, red-eye reduction mode.'); + case 0x0059: + return Pel::tra('Flash fired, auto mode, red-eye reduction mode.'); + case 0x005d: + return Pel::tra('Flash fired, auto mode, return light not detected, red-eye reduction mode.'); + case 0x005f: + return Pel::tra('Flash fired, auto mode, return light detected, red-eye reduction mode.'); + default: + return $this->value[0]; + } + + case PelTag::CUSTOM_RENDERED: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Normal process'); + case 1: + return Pel::tra('Custom process'); + default: + return $this->value[0]; + } + + case PelTag::EXPOSURE_MODE: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Auto exposure'); + case 1: + return Pel::tra('Manual exposure'); + case 2: + return Pel::tra('Auto bracket'); + default: + return $this->value[0]; + } + + case PelTag::WHITE_BALANCE: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Auto white balance'); + case 1: + return Pel::tra('Manual white balance'); + default: + return $this->value[0]; + } + + case PelTag::SCENE_CAPTURE_TYPE: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Standard'); + case 1: + return Pel::tra('Landscape'); + case 2: + return Pel::tra('Portrait'); + case 3: + return Pel::tra('Night scene'); + default: + return $this->value[0]; + } + + case PelTag::GAIN_CONTROL: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Normal'); + case 1: + return Pel::tra('Low gain up'); + case 2: + return Pel::tra('High gain up'); + case 3: + return Pel::tra('Low gain down'); + case 4: + return Pel::tra('High gain down'); + default: + return $this->value[0]; + } + + case PelTag::SATURATION: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Normal'); + case 1: + return Pel::tra('Low saturation'); + case 2: + return Pel::tra('High saturation'); + default: + return $this->value[0]; + } + + case PelTag::CONTRAST: + case PelTag::SHARPNESS: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Normal'); + case 1: + return Pel::tra('Soft'); + case 2: + return Pel::tra('Hard'); + default: + return $this->value[0]; + } + + case PelTag::SUBJECT_DISTANCE_RANGE: + //CC (e->components, 1, v); + switch ($this->value[0]) { + case 0: + return Pel::tra('Unknown'); + case 1: + return Pel::tra('Macro'); + case 2: + return Pel::tra('Close view'); + case 3: + return Pel::tra('Distant view'); + default: + return $this->value[0]; + } + + case PelTag::SUBJECT_AREA: + switch ($this->components) { + case 2: + return Pel::fmt('(x,y) = (%d,%d)', $this->value[0], $this->value[1]); + case 3: + return Pel::fmt('Within distance %d of (x,y) = (%d,%d)', + $this->value[0], $this->value[1], $this->value[2]); + case 4: + return Pel::fmt('Within rectangle (width %d, height %d) around (x,y) = (%d,%d)', + $this->value[0], $this->value[1], + $this->value[2], $this->value[3]); + + default: + return Pel::fmt('Unexpected number of components (%d, expected 2, 3, or 4).', $this->components); + } + + default: + return parent::getText($brief); + } + } +} + + +/** + * Class for holding signed shorts. + * + * This class can hold shorts, either just a single short or an array + * of shorts. The class will be used to manipulate any of the Exif + * tags which has format {@link PelFormat::SSHORT}. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntrySShort extends PelEntryNumber { + + /** + * Make a new entry that can hold a signed short. + * + * The method accept several integer arguments. The {@link + * getValue} method will always return an array except for when a + * single integer argument is given here. + * + * @param PelTag the tag which this entry represents. This + * should be one of the constants defined in {@link PelTag} + * which has format {@link PelFormat::SSHORT}. + * + * @param int $value... the signed short(s) that this entry will + * represent. The argument passed must obey the same rules as the + * argument to {@link setValue}, namely that it should be within + * range of a signed short, that is between -32768 to 32767 + * (inclusive). If not, then a {@link PelOverFlowException} will be + * thrown. + */ + function __construct($tag /* $value... */) { + $this->tag = $tag; + $this->min = -32768; + $this->max = 32767; + $this->format = PelFormat::SSHORT; + + $value = func_get_args(); + array_shift($value); + $this->setValueArray($value); + } + + + /** + * Convert a number into bytes. + * + * @param int the number that should be converted. + * + * @param PelByteOrder one of {@link PelConvert::LITTLE_ENDIAN} and + * {@link PelConvert::BIG_ENDIAN}, specifying the target byte order. + * + * @return string bytes representing the number given. + */ + function numberToBytes($number, $order) { + return PelConvert::sShortToBytes($number, $order); + } +} + diff --git a/modules/autorotate/lib/pel/PelEntryUndefined.php b/modules/autorotate/lib/pel/PelEntryUndefined.php new file mode 100644 index 0000000..3253c99 --- /dev/null +++ b/modules/autorotate/lib/pel/PelEntryUndefined.php @@ -0,0 +1,416 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes used to hold data for Exif tags of format undefined. + * + * This file contains the base class {@link PelEntryUndefined} and + * the subclasses {@link PelEntryUserComment} which should be used + * to manage the {@link PelTag::USER_COMMENT} tag, and {@link + * PelEntryVersion} which is used to manage entries with version + * information. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelEntry.php'); +/**#@-*/ + + +/** + * Class for holding data of any kind. + * + * This class can hold bytes of undefined format. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryUndefined extends PelEntry { + + /** + * Make a new PelEntry that can hold undefined data. + * + * @param PelTag the tag which this entry represents. This + * should be one of the constants defined in {@link PelTag}, + * e.g., {@link PelTag::SCENE_TYPE}, {@link + * PelTag::MAKER_NOTE} or any other tag with format {@link + * PelFormat::UNDEFINED}. + * + * @param string the data that this entry will be holding. Since + * the format is undefined, no checking will be done on the data. + */ + function __construct($tag, $data = '') { + $this->tag = $tag; + $this->format = PelFormat::UNDEFINED; + $this->setValue($data); + } + + + /** + * Set the data of this undefined entry. + * + * @param string the data that this entry will be holding. Since + * the format is undefined, no checking will be done on the data. + */ + function setValue($data) { + $this->components = strlen($data); + $this->bytes = $data; + } + + + /** + * Get the data of this undefined entry. + * + * @return string the data that this entry is holding. + */ + function getValue() { + return $this->bytes; + } + + + /** + * Get the value of this entry as text. + * + * The value will be returned in a format suitable for presentation. + * + * @param boolean some values can be returned in a long or more + * brief form, and this parameter controls that. + * + * @return string the value as text. + */ + function getText($brief = false) { + switch ($this->tag) { + case PelTag::FILE_SOURCE: + //CC (e->components, 1, v); + switch (ord($this->bytes{0})) { + case 0x03: + return 'DSC'; + default: + return sprintf('0x%02X', ord($this->bytes{0})); + } + + case PelTag::SCENE_TYPE: + //CC (e->components, 1, v); + switch (ord($this->bytes{0})) { + case 0x01: + return 'Directly photographed'; + default: + return sprintf('0x%02X', ord($this->bytes{0})); + } + + case PelTag::COMPONENTS_CONFIGURATION: + //CC (e->components, 4, v); + $v = ''; + for ($i = 0; $i < 4; $i++) { + switch (ord($this->bytes{$i})) { + case 0: + $v .= '-'; + break; + case 1: + $v .= 'Y'; + break; + case 2: + $v .= 'Cb'; + break; + case 3: + $v .= 'Cr'; + break; + case 4: + $v .= 'R'; + break; + case 5: + $v .= 'G'; + break; + case 6: + $v .= 'B'; + break; + default: + $v .= 'reserved'; + break; + } + if ($i < 3) $v .= ' '; + } + return $v; + + case PelTag::MAKER_NOTE: + // TODO: handle maker notes. + return $this->components . ' bytes unknown MakerNote data'; + + default: + return '(undefined)'; + } + } + +} + + +/** + * Class for a user comment. + * + * This class is used to hold user comments, which can come in several + * different character encodings. The Exif standard specifies a + * certain format of the {@link PelTag::USER_COMMENT user comment + * tag}, and this class will make sure that the format is kept. + * + * The most basic use of this class simply stores an ASCII encoded + * string for later retrieval using {@link getValue}: + * + * <code> + * $entry = new PelEntryUserComment('An ASCII string'); + * echo $entry->getValue(); + * </code> + * + * The string can be encoded with a different encoding, and if so, the + * encoding must be given using the second argument. The Exif + * standard specifies three known encodings: 'ASCII', 'JIS', and + * 'Unicode'. If the user comment is encoded using a character + * encoding different from the tree known encodings, then the empty + * string should be passed as encoding, thereby specifying that the + * encoding is undefined. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryUserComment extends PelEntryUndefined { + + /** + * The user comment. + * + * @var string + */ + private $comment; + + /** + * The encoding. + * + * This should be one of 'ASCII', 'JIS', 'Unicode', or ''. + * + * @var string + */ + private $encoding; + + /** + * Make a new entry for holding a user comment. + * + * @param string the new user comment. + * + * @param string the encoding of the comment. This should be either + * 'ASCII', 'JIS', 'Unicode', or the empty string specifying an + * undefined encoding. + */ + function __construct($comment = '', $encoding = 'ASCII') { + parent::__construct(PelTag::USER_COMMENT); + $this->setValue($comment, $encoding); + } + + + /** + * Set the user comment. + * + * @param string the new user comment. + * + * @param string the encoding of the comment. This should be either + * 'ASCII', 'JIS', 'Unicode', or the empty string specifying an + * unknown encoding. + */ + function setValue($comment = '', $encoding = 'ASCII') { + $this->comment = $comment; + $this->encoding = $encoding; + parent::setValue(str_pad($encoding, 8, chr(0)) . $comment); + } + + + /** + * Returns the user comment. + * + * The comment is returned with the same character encoding as when + * it was set using {@link setValue} or {@link __construct the + * constructor}. + * + * @return string the user comment. + */ + function getValue() { + return $this->comment; + } + + + /** + * Returns the encoding. + * + * @return string the encoding of the user comment. + */ + function getEncoding() { + return $this->encoding; + } + + + /** + * Returns the user comment. + * + * @return string the user comment. + */ + function getText($brief = false) { + return $this->comment; + } + +} + + +/** + * Class to hold version information. + * + * There are three Exif entries that hold version information: the + * {@link PelTag::EXIF_VERSION}, {@link + * PelTag::FLASH_PIX_VERSION}, and {@link + * PelTag::INTEROPERABILITY_VERSION} tags. This class manages + * those tags. + * + * The class is used in a very straight-forward way: + * <code> + * $entry = new PelEntryVersion(PelTag::EXIF_VERSION, 2.2); + * </code> + * This creates an entry for an file complying to the Exif 2.2 + * standard. It is easy to test for standards level of an unknown + * entry: + * <code> + * if ($entry->getTag() == PelTag::EXIF_VERSION && + * $entry->getValue() > 2.0) { + * echo 'Recent Exif version.'; + * } + * </code> + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelEntryVersion extends PelEntryUndefined { + + /** + * The version held by this entry. + * + * @var float + */ + private $version; + + + /** + * Make a new entry for holding a version. + * + * @param PelTag the tag. This should be one of {@link + * PelTag::EXIF_VERSION}, {@link PelTag::FLASH_PIX_VERSION}, + * or {@link PelTag::INTEROPERABILITY_VERSION}. + * + * @param float the version. The size of the entries leave room for + * exactly four digits: two digits on either side of the decimal + * point. + */ + function __construct($tag, $version = 0.0) { + parent::__construct($tag); + $this->setValue($version); + } + + + /** + * Set the version held by this entry. + * + * @param float the version. The size of the entries leave room for + * exactly four digits: two digits on either side of the decimal + * point. + */ + function setValue($version = 0.0) { + $this->version = $version; + $major = floor($version); + $minor = ($version - $major)*100; + parent::setValue(sprintf('%02.0f%02.0f', $major, $minor)); + } + + + /** + * Return the version held by this entry. + * + * @return float the version. This will be the same as the value + * given to {@link setValue} or {@link __construct the + * constructor}. + */ + function getValue() { + return $this->version; + } + + + /** + * Return a text string with the version. + * + * @param boolean controls if the output should be brief. Brief + * output omits the word 'Version' so the result is just 'Exif x.y' + * instead of 'Exif Version x.y' if the entry holds information + * about the Exif version --- the output for FlashPix is similar. + * + * @return string the version number with the type of the tag, + * either 'Exif' or 'FlashPix'. + */ + function getText($brief = false) { + $v = $this->version; + + /* Versions numbers like 2.0 would be output as just 2 if we don't + * add the '.0' ourselves. */ + if (floor($this->version) == $this->version) + $v .= '.0'; + + switch ($this->tag) { + case PelTag::EXIF_VERSION: + if ($brief) + return Pel::fmt('Exif %s', $v); + else + return Pel::fmt('Exif Version %s', $v); + + case PelTag::FLASH_PIX_VERSION: + if ($brief) + return Pel::fmt('FlashPix %s', $v); + else + return Pel::fmt('FlashPix Version %s', $v); + + case PelTag::INTEROPERABILITY_VERSION: + if ($brief) + return Pel::fmt('Interoperability %s', $v); + else + return Pel::fmt('Interoperability Version %s', $v); + } + + if ($brief) + return $v; + else + return Pel::fmt('Version %s', $v); + + } + +} + diff --git a/modules/autorotate/lib/pel/PelException.php b/modules/autorotate/lib/pel/PelException.php new file mode 100644 index 0000000..1af4a02 --- /dev/null +++ b/modules/autorotate/lib/pel/PelException.php @@ -0,0 +1,87 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Standard PEL exception. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/** + * A printf() capable exception. + * + * This class is a simple extension of the standard Exception class in + * PHP, and all the methods defined there retain their original + * meaning. + * + * @package PEL + * @subpackage Exception + */ +class PelException extends Exception { + + /** + * Construct a new PEL exception. + * + * @param string $fmt an optional format string can be given. It + * will be used as a format string for vprintf(). The remaining + * arguments will be available for the format string as usual with + * vprintf(). + * + * @param mixed $args,... any number of arguments to be used with + * the format string. + */ + function __construct(/* fmt, args... */) { + $args = func_get_args(); + $fmt = array_shift($args); + parent::__construct(vsprintf($fmt, $args)); + } +} + + +/** + * Exception throw if invalid data is found. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + * @subpackage Exception + */ +class PelInvalidDataException extends PelException {} + +/** + * Exception throw if an invalid argument is passed. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + * @subpackage Exception + */ +class PelInvalidArgumentException extends PelException {} + diff --git a/modules/autorotate/lib/pel/PelExif.php b/modules/autorotate/lib/pel/PelExif.php new file mode 100644 index 0000000..c07f88d --- /dev/null +++ b/modules/autorotate/lib/pel/PelExif.php @@ -0,0 +1,175 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes for dealing with Exif data. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelJpegContent.php'); +require_once('PelException.php'); +require_once('PelFormat.php'); +require_once('PelEntry.php'); +require_once('PelTiff.php'); +require_once('PelIfd.php'); +require_once('PelTag.php'); +require_once('Pel.php'); +/**#@-*/ + + +/** + * Class representing Exif data. + * + * Exif data resides as {@link PelJpegContent data} and consists of a + * header followed by a number of {@link PelJpegIfd IFDs}. + * + * The interesting method in this class is {@link getTiff()} which + * will return the {@link PelTiff} object which really holds the data + * which one normally think of when talking about Exif data. This is + * because Exif data is stored as an extension of the TIFF file + * format. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelExif extends PelJpegContent { + + /** + * Exif header. + * + * The Exif data must start with these six bytes to be considered + * valid. + */ + const EXIF_HEADER = "Exif\0\0"; + + /** + * The PelTiff object contained within. + * + * @var PelTiff + */ + private $tiff = null; + + + /** + * Construct a new Exif object. + * + * The new object will be empty --- use the {@link load()} method to + * load Exif data from a {@link PelDataWindow} object, or use the + * {@link setTiff()} to change the {@link PelTiff} object, which is + * the true holder of the Exif {@link PelEntry entries}. + */ + function __construct() { + + } + + + /** + * Load and parse Exif data. + * + * This will populate the object with Exif data, contained as a + * {@link PelTiff} object. This TIFF object can be accessed with + * the {@link getTiff()} method. + */ + function load(PelDataWindow $d) { + Pel::debug('Parsing %d bytes of Exif data...', $d->getSize()); + + /* There must be at least 6 bytes for the Exif header. */ + if ($d->getSize() < 6) + throw new PelInvalidDataException('Expected at least 6 bytes of Exif ' . + 'data, found just %d bytes.', + $d->getSize()); + + /* Verify the Exif header */ + if ($d->strcmp(0, self::EXIF_HEADER)) { + $d->setWindowStart(strlen(self::EXIF_HEADER)); + } else { + throw new PelInvalidDataException('Exif header not found.'); + } + + /* The rest of the data is TIFF data. */ + $this->tiff = new PelTiff(); + $this->tiff->load($d); + } + + + /** + * Change the TIFF information. + * + * Exif data is really stored as TIFF data, and this method can be + * used to change this data from one {@link PelTiff} object to + * another. + * + * @param PelTiff the new TIFF object. + */ + function setTiff(PelTiff $tiff) { + $this->tiff = $tiff; + } + + + /** + * Get the underlying TIFF object. + * + * The actual Exif data is stored in a {@link PelTiff} object, and + * this method provides access to it. + * + * @return PelTiff the TIFF object with the Exif data. + */ + function getTiff() { + return $this->tiff; + } + + + /** + * Produce bytes for the Exif data. + * + * @return string bytes representing this object. + */ + function getBytes() { + return self::EXIF_HEADER . $this->tiff->getBytes(); + } + + + /** + * Return a string representation of this object. + * + * @return string a string describing this object. This is mostly + * useful for debugging. + */ + function __toString() { + return Pel::tra("Dumping Exif data...\n") . + $this->tiff->__toString(); + } + +} + diff --git a/modules/autorotate/lib/pel/PelFormat.php b/modules/autorotate/lib/pel/PelFormat.php new file mode 100644 index 0000000..540f7ca --- /dev/null +++ b/modules/autorotate/lib/pel/PelFormat.php @@ -0,0 +1,226 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Namespace for functions operating on Exif formats. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/** + * Namespace for functions operating on Exif formats. + * + * This class defines the constants that are to be used whenever one + * has to refer to the format of an Exif tag. They will be + * collectively denoted by the pseudo-type PelFormat throughout the + * documentation. + * + * All the methods defined here are static, and they all operate on a + * single argument which should be one of the class constants. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelFormat { + + /** + * Unsigned byte. + * + * Each component will be an unsigned 8-bit integer with a value + * between 0 and 255. + * + * Modelled with the {@link PelEntryByte} class. + */ + const BYTE = 1; + + /** + * ASCII string. + * + * Each component will be an ASCII character. + * + * Modelled with the {@link PelEntryAscii} class. + */ + const ASCII = 2; + + /** + * Unsigned short. + * + * Each component will be an unsigned 16-bit integer with a value + * between 0 and 65535. + * + * Modelled with the {@link PelEntryShort} class. + */ + const SHORT = 3; + + /** + * Unsigned long. + * + * Each component will be an unsigned 32-bit integer with a value + * between 0 and 4294967295. + * + * Modelled with the {@link PelEntryLong} class. + */ + const LONG = 4; + + /** + * Unsigned rational number. + * + * Each component will consist of two unsigned 32-bit integers + * denoting the enumerator and denominator. Each integer will have + * a value between 0 and 4294967295. + * + * Modelled with the {@link PelEntryRational} class. + */ + const RATIONAL = 5; + + /** + * Signed byte. + * + * Each component will be a signed 8-bit integer with a value + * between -128 and 127. + * + * Modelled with the {@link PelEntrySByte} class. + */ + const SBYTE = 6; + + /** + * Undefined byte. + * + * Each component will be a byte with no associated interpretation. + * + * Modelled with the {@link PelEntryUndefined} class. + */ + const UNDEFINED = 7; + + /** + * Signed short. + * + * Each component will be a signed 16-bit integer with a value + * between -32768 and 32767. + * + * Modelled with the {@link PelEntrySShort} class. + */ + const SSHORT = 8; + + /** + * Signed long. + * + * Each component will be a signed 32-bit integer with a value + * between -2147483648 and 2147483647. + * + * Modelled with the {@link PelEntrySLong} class. + */ + const SLONG = 9; + + /** + * Signed rational number. + * + * Each component will consist of two signed 32-bit integers + * denoting the enumerator and denominator. Each integer will have + * a value between -2147483648 and 2147483647. + * + * Modelled with the {@link PelEntrySRational} class. + */ + const SRATIONAL = 10; + + /** + * Floating point number. + * + * Entries with this format are not currently implemented. + */ + const FLOAT = 11; + + /** + * Double precision floating point number. + * + * Entries with this format are not currently implemented. + */ + const DOUBLE = 12; + + + /** + * Returns the name of a format. + * + * @param PelFormat the format. + * + * @return string the name of the format, e.g., 'Ascii' for the + * {@link ASCII} format etc. + */ + static function getName($type) { + switch ($type) { + case self::ASCII: return 'Ascii'; + case self::BYTE: return 'Byte'; + case self::SHORT: return 'Short'; + case self::LONG: return 'Long'; + case self::RATIONAL: return 'Rational'; + case self::SBYTE: return 'SByte'; + case self::SSHORT: return 'SShort'; + case self::SLONG: return 'SLong'; + case self::SRATIONAL: return 'SRational'; + case self::FLOAT: return 'Float'; + case self::DOUBLE: return 'Double'; + case self::UNDEFINED: return 'Undefined'; + default: + return Pel::fmt('Unknown format: 0x%X', $type); + } + } + + + /** + * Return the size of components in a given format. + * + * @param PelFormat the format. + * + * @return the size in bytes needed to store one component with the + * given format. + */ + static function getSize($type) { + switch ($type) { + case self::ASCII: return 1; + case self::BYTE: return 1; + case self::SHORT: return 2; + case self::LONG: return 4; + case self::RATIONAL: return 8; + case self::SBYTE: return 1; + case self::SSHORT: return 2; + case self::SLONG: return 4; + case self::SRATIONAL: return 8; + case self::FLOAT: return 4; + case self::DOUBLE: return 8; + case self::UNDEFINED: return 1; + default: + return Pel::fmt('Unknown format: 0x%X', $type); + } + } + +} + diff --git a/modules/autorotate/lib/pel/PelIfd.php b/modules/autorotate/lib/pel/PelIfd.php new file mode 100644 index 0000000..851bb30 --- /dev/null +++ b/modules/autorotate/lib/pel/PelIfd.php @@ -0,0 +1,1200 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes for dealing with Exif IFDs. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelEntryUndefined.php'); +require_once('PelEntryRational.php'); +require_once('PelDataWindow.php'); +require_once('PelEntryAscii.php'); +require_once('PelEntryShort.php'); +require_once('PelEntryByte.php'); +require_once('PelEntryLong.php'); +require_once('PelException.php'); +require_once('PelFormat.php'); +require_once('PelEntry.php'); +require_once('PelTag.php'); +require_once('Pel.php'); +/**#@-*/ + + +/** + * Exception indicating a general problem with the IFD. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + * @subpackage Exception + */ +class PelIfdException extends PelException {} + +/** + * Class representing an Image File Directory (IFD). + * + * {@link PelTiff TIFF data} is structured as a number of Image File + * Directories, IFDs for short. Each IFD contains a number of {@link + * PelEntry entries}, some data and finally a link to the next IFD. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelIfd implements IteratorAggregate, ArrayAccess { + + /** + * Main image IFD. + * + * Pass this to the constructor when creating an IFD which will be + * the IFD of the main image. + */ + const IFD0 = 0; + + /** + * Thumbnail image IFD. + * + * Pass this to the constructor when creating an IFD which will be + * the IFD of the thumbnail image. + */ + const IFD1 = 1; + + /** + * Exif IFD. + * + * Pass this to the constructor when creating an IFD which will be + * the Exif sub-IFD. + */ + const EXIF = 2; + + /** + * GPS IFD. + * + * Pass this to the constructor when creating an IFD which will be + * the GPS sub-IFD. + */ + const GPS = 3; + + /** + * Interoperability IFD. + * + * Pass this to the constructor when creating an IFD which will be + * the interoperability sub-IFD. + */ + const INTEROPERABILITY = 4; + + /** + * The entries held by this directory. + * + * Each tag in the directory is represented by a {@link PelEntry} + * object in this array. + * + * @var array + */ + private $entries = array(); + + /** + * The type of this directory. + * + * Initialized in the constructor. Must be one of {@link IFD0}, + * {@link IFD1}, {@link EXIF}, {@link GPS}, or {@link + * INTEROPERABILITY}. + * + * @var int + */ + private $type; + + /** + * The next directory. + * + * This will be initialized in the constructor, or be left as null + * if this is the last directory. + * + * @var PelIfd + */ + private $next = null; + + /** + * Sub-directories pointed to by this directory. + * + * This will be an array of ({@link PelTag}, {@link PelIfd}) pairs. + * + * @var array + */ + private $sub = array(); + + /** + * The thumbnail data. + * + * This will be initialized in the constructor, or be left as null + * if there are no thumbnail as part of this directory. + * + * @var PelDataWindow + */ + private $thumb_data = null; + // TODO: use this format to choose between the + // JPEG_INTERCHANGE_FORMAT and STRIP_OFFSETS tags. + // private $thumb_format; + + + /** + * Construct a new Image File Directory (IFD). + * + * The IFD will be empty, use the {@link addEntry()} method to add + * an {@link PelEntry}. Use the {@link setNext()} method to link + * this IFD to another. + * + * @param int type the type of this IFD. Must be one of {@link + * IFD0}, {@link IFD1}, {@link EXIF}, {@link GPS}, or {@link + * INTEROPERABILITY}. An {@link PelIfdException} will be thrown + * otherwise. + */ + function __construct($type) { + if ($type != PelIfd::IFD0 && $type != PelIfd::IFD1 && + $type != PelIfd::EXIF && $type != PelIfd::GPS && + $type != PelIfd::INTEROPERABILITY) + throw new PelIfdException('Unknown IFD type: %d', $type); + + $this->type = $type; + } + + + /** + * Load data into a Image File Directory (IFD). + * + * @param PelDataWindow the data window that will provide the data. + * + * @param int the offset within the window where the directory will + * be found. + */ + function load(PelDataWindow $d, $offset) { + $thumb_offset = 0; + $thumb_length = 0; + + Pel::debug('Constructing IFD at offset %d from %d bytes...', + $offset, $d->getSize()); + + /* Read the number of entries */ + $n = $d->getShort($offset); + Pel::debug('Loading %d entries...', $n); + + $offset += 2; + + /* Check if we have enough data. */ + if ($offset + 12 * $n > $d->getSize()) { + $n = floor(($offset - $d->getSize()) / 12); + Pel::maybeThrow(new PelIfdException('Adjusted to: %d.', $n)); + } + + for ($i = 0; $i < $n; $i++) { + // TODO: increment window start instead of using offsets. + $tag = $d->getShort($offset + 12 * $i); + Pel::debug('Loading entry with tag 0x%04X: %s (%d of %d)...', + $tag, PelTag::getName($this->type, $tag), $i + 1, $n); + + switch ($tag) { + case PelTag::EXIF_IFD_POINTER: + case PelTag::GPS_INFO_IFD_POINTER: + case PelTag::INTEROPERABILITY_IFD_POINTER: + $o = $d->getLong($offset + 12 * $i + 8); + Pel::debug('Found sub IFD at offset %d', $o); + + /* Map tag to IFD type. */ + if ($tag == PelTag::EXIF_IFD_POINTER) + $type = PelIfd::EXIF; + elseif ($tag == PelTag::GPS_INFO_IFD_POINTER) + $type = PelIfd::GPS; + elseif ($tag == PelTag::INTEROPERABILITY_IFD_POINTER) + $type = PelIfd::INTEROPERABILITY; + + $this->sub[$type] = new PelIfd($type); + $this->sub[$type]->load($d, $o); + break; + case PelTag::JPEG_INTERCHANGE_FORMAT: + $thumb_offset = $d->getLong($offset + 12 * $i + 8); + $this->safeSetThumbnail($d, $thumb_offset, $thumb_length); + break; + case PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH: + $thumb_length = $d->getLong($offset + 12 * $i + 8); + $this->safeSetThumbnail($d, $thumb_offset, $thumb_length); + break; + default: + $format = $d->getShort($offset + 12 * $i + 2); + $components = $d->getLong($offset + 12 * $i + 4); + + /* The data size. If bigger than 4 bytes, the actual data is + * not in the entry but somewhere else, with the offset stored + * in the entry. + */ + $s = PelFormat::getSize($format) * $components; + if ($s > 0) { + $doff = $offset + 12 * $i + 8; + if ($s > 4) + $doff = $d->getLong($doff); + + $data = $d->getClone($doff, $s); + } else { + $data = new PelDataWindow(); + } + + try { + $entry = $this->newEntryFromData($tag, $format, $components, $data); + $this->addEntry($entry); + } catch (PelException $e) { + /* Throw the exception when running in strict mode, store + * otherwise. */ + Pel::maybeThrow($e); + } + + /* The format of the thumbnail is stored in this tag. */ + // TODO: handle TIFF thumbnail. + // if ($tag == PelTag::COMPRESSION) { + // $this->thumb_format = $data->getShort(); + // } + break; + } + } + + /* Offset to next IFD */ + $o = $d->getLong($offset + 12 * $n); + Pel::debug('Current offset is %d, link at %d points to %d.', + $offset, $offset + 12 * $n, $o); + + if ($o > 0) { + /* Sanity check: we need 6 bytes */ + if ($o > $d->getSize() - 6) { + Pel::maybeThrow(new PelIfdException('Bogus offset to next IFD: ' . + '%d > %d!', + $o, $d->getSize() - 6)); + } else { + if ($this->type == PelIfd::IFD1) // IFD1 shouldn't link further... + Pel::maybeThrow(new PelIfdException('IFD1 links to another IFD!')); + + $this->next = new PelIfd(PelIfd::IFD1); + $this->next->load($d, $o); + } + } else { + Pel::debug('Last IFD.'); + } + } + + + /** + * Make a new entry from a bunch of bytes. + * + * This method will create the proper subclass of {@link PelEntry} + * corresponding to the {@link PelTag} and {@link PelFormat} given. + * The entry will be initialized with the data given. + * + * Please note that the data you pass to this method should come + * from an image, that is, it should be raw bytes. If instead you + * want to create an entry for holding, say, an short integer, then + * create a {@link PelEntryShort} object directly and load the data + * into it. + * + * A {@link PelUnexpectedFormatException} is thrown if a mismatch is + * discovered between the tag and format, and likewise a {@link + * PelWrongComponentCountException} is thrown if the number of + * components does not match the requirements of the tag. The + * requirements for a given tag (if any) can be found in the + * documentation for {@link PelTag}. + * + * @param PelTag the tag of the entry. + * + * @param PelFormat the format of the entry. + * + * @param int the components in the entry. + * + * @param PelDataWindow the data which will be used to construct the + * entry. + * + * @return PelEntry a newly created entry, holding the data given. + */ + function newEntryFromData($tag, $format, $components, PelDataWindow $data) { + + /* First handle tags for which we have a specific PelEntryXXX + * class. */ + + switch ($this->type) { + + case self::IFD0: + case self::IFD1: + case self::EXIF: + case self::INTEROPERABILITY: + + switch ($tag) { + case PelTag::DATE_TIME: + case PelTag::DATE_TIME_ORIGINAL: + case PelTag::DATE_TIME_DIGITIZED: + if ($format != PelFormat::ASCII) + throw new PelUnexpectedFormatException($this->type, $tag, $format, + PelFormat::ASCII); + + if ($components != 20) + throw new PelWrongComponentCountException($this->type, $tag, $components, 20); + + // TODO: handle timezones. + return new PelEntryTime($tag, $data->getBytes(0, -1), PelEntryTime::EXIF_STRING); + + case PelTag::COPYRIGHT: + if ($format != PelFormat::ASCII) + throw new PelUnexpectedFormatException($this->type, $tag, $format, + PelFormat::ASCII); + + $v = explode("\0", trim($data->getBytes(), ' ')); + return new PelEntryCopyright($v[0], $v[1]); + + case PelTag::EXIF_VERSION: + case PelTag::FLASH_PIX_VERSION: + case PelTag::INTEROPERABILITY_VERSION: + if ($format != PelFormat::UNDEFINED) + throw new PelUnexpectedFormatException($this->type, $tag, $format, + PelFormat::UNDEFINED); + + return new PelEntryVersion($tag, $data->getBytes() / 100); + + case PelTag::USER_COMMENT: + if ($format != PelFormat::UNDEFINED) + throw new PelUnexpectedFormatException($this->type, $tag, $format, + PelFormat::UNDEFINED); + if ($data->getSize() < 8) { + return new PelEntryUserComment(); + } else { + return new PelEntryUserComment($data->getBytes(8), + rtrim($data->getBytes(0, 8))); + } + + case PelTag::XP_TITLE: + case PelTag::XP_COMMENT: + case PelTag::XP_AUTHOR: + case PelTag::XP_KEYWORDS: + case PelTag::XP_SUBJECT: + if ($format != PelFormat::BYTE) + throw new PelUnexpectedFormatException($this->type, $tag, $format, + PelFormat::BYTE); + + $v = ''; + for ($i = 0; $i < $components; $i++) { + $b = $data->getByte($i); + /* Convert the byte to a character if it is non-null --- + * information about the character encoding of these entries + * would be very nice to have! So far my tests have shown + * that characters in the Latin-1 character set are stored in + * a single byte followed by a NULL byte. */ + if ($b != 0) + $v .= chr($b); + } + + return new PelEntryWindowsString($tag, $v); + } + + case self::GPS: + + default: + /* Then handle the basic formats. */ + switch ($format) { + case PelFormat::BYTE: + $v = new PelEntryByte($tag); + for ($i = 0; $i < $components; $i++) + $v->addNumber($data->getByte($i)); + return $v; + + case PelFormat::SBYTE: + $v = new PelEntrySByte($tag); + for ($i = 0; $i < $components; $i++) + $v->addNumber($data->getSByte($i)); + return $v; + + case PelFormat::ASCII: + return new PelEntryAscii($tag, $data->getBytes(0, -1)); + + case PelFormat::SHORT: + $v = new PelEntryShort($tag); + for ($i = 0; $i < $components; $i++) + $v->addNumber($data->getShort($i*2)); + return $v; + + case PelFormat::SSHORT: + $v = new PelEntrySShort($tag); + for ($i = 0; $i < $components; $i++) + $v->addNumber($data->getSShort($i*2)); + return $v; + + case PelFormat::LONG: + $v = new PelEntryLong($tag); + for ($i = 0; $i < $components; $i++) + $v->addNumber($data->getLong($i*4)); + return $v; + + case PelFormat::SLONG: + $v = new PelEntrySLong($tag); + for ($i = 0; $i < $components; $i++) + $v->addNumber($data->getSLong($i*4)); + return $v; + + case PelFormat::RATIONAL: + $v = new PelEntryRational($tag); + for ($i = 0; $i < $components; $i++) + $v->addNumber($data->getRational($i*8)); + return $v; + + case PelFormat::SRATIONAL: + $v = new PelEntrySRational($tag); + for ($i = 0; $i < $components; $i++) + $v->addNumber($data->getSRational($i*8)); + return $v; + + case PelFormat::UNDEFINED: + return new PelEntryUndefined($tag, $data->getBytes()); + + default: + throw new PelException('Unsupported format: %s', + PelFormat::getName($format)); + } + } + } + + + + + /** + * Extract thumbnail data safely. + * + * It is safe to call this method repeatedly with either the offset + * or the length set to zero, since it requires both of these + * arguments to be positive before the thumbnail is extracted. + * + * When both parameters are set it will check the length against the + * available data and adjust as necessary. Only then is the + * thumbnail data loaded. + * + * @param PelDataWindow the data from which the thumbnail will be + * extracted. + * + * @param int the offset into the data. + * + * @param int the length of the thumbnail. + */ + private function safeSetThumbnail(PelDataWindow $d, $offset, $length) { + /* Load the thumbnail if both the offset and the length is + * available. */ + if ($offset > 0 && $length > 0) { + /* Some images have a broken length, so we try to carefully + * check the length before we store the thumbnail. */ + if ($offset + $length > $d->getSize()) { + Pel::maybeThrow(new PelIfdException('Thumbnail length %d bytes ' . + 'adjusted to %d bytes.', + $length, + $d->getSize() - $offset)); + $length = $d->getSize() - $offset; + } + + /* Now set the thumbnail normally. */ + $this->setThumbnail($d->getClone($offset, $length)); + } + } + + + /** + * Set thumbnail data. + * + * Use this to embed an arbitrary JPEG image within this IFD. The + * data will be checked to ensure that it has a proper {@link + * PelJpegMarker::EOI} at the end. If not, then the length is + * adjusted until one if found. An {@link PelIfdException} might be + * thrown (depending on {@link Pel::$strict}) this case. + * + * @param PelDataWindow the thumbnail data. + */ + function setThumbnail(PelDataWindow $d) { + $size = $d->getSize(); + /* Now move backwards until we find the EOI JPEG marker. */ + while ($d->getByte($size - 2) != 0xFF || + $d->getByte($size - 1) != PelJpegMarker::EOI) { + $size--; + } + + if ($size != $d->getSize()) + Pel::maybeThrow(new PelIfdException('Decrementing thumbnail size ' . + 'to %d bytes', $size)); + + $this->thumb_data = $d->getClone(0, $size); + } + + + /** + * Get the type of this directory. + * + * @return int of {@link PelIfd::IFD0}, {@link PelIfd::IFD1}, {@link + * PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link + * PelIfd::INTEROPERABILITY}. + */ + function getType() { + return $this->type; + } + + + /** + * Is a given tag valid for this IFD? + * + * Different types of IFDs can contain different kinds of tags --- + * the {@link IFD0} type, for example, cannot contain a {@link + * PelTag::GPS_LONGITUDE} tag. + * + * A special exception is tags with values above 0xF000. They are + * treated as private tags and will be allowed everywhere (use this + * for testing or for implementing your own types of tags). + * + * @param PelTag the tag. + * + * @return boolean true if the tag is considered valid in this IFD, + * false otherwise. + * + * @see getValidTags() + */ + function isValidTag($tag) { + return $tag > 0xF000 || in_array($tag, $this->getValidTags()); + } + + + /** + * Returns a list of valid tags for this IFD. + * + * @return array an array of {@link PelTag}s which are valid for + * this IFD. + */ + function getValidTags() { + switch ($this->type) { + case PelIfd::IFD0: + case PelIfd::IFD1: + return array(PelTag::IMAGE_WIDTH, + PelTag::IMAGE_LENGTH, + PelTag::BITS_PER_SAMPLE, + PelTag::COMPRESSION, + PelTag::PHOTOMETRIC_INTERPRETATION, + PelTag::IMAGE_DESCRIPTION, + PelTag::MAKE, + PelTag::MODEL, + PelTag::STRIP_OFFSETS, + PelTag::ORIENTATION, + PelTag::SAMPLES_PER_PIXEL, + PelTag::ROWS_PER_STRIP, + PelTag::STRIP_BYTE_COUNTS, + PelTag::X_RESOLUTION, + PelTag::Y_RESOLUTION, + PelTag::PLANAR_CONFIGURATION, + PelTag::RESOLUTION_UNIT, + PelTag::TRANSFER_FUNCTION, + PelTag::SOFTWARE, + PelTag::DATE_TIME, + PelTag::ARTIST, + PelTag::WHITE_POINT, + PelTag::PRIMARY_CHROMATICITIES, + PelTag::JPEG_INTERCHANGE_FORMAT, + PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH, + PelTag::YCBCR_COEFFICIENTS, + PelTag::YCBCR_SUB_SAMPLING, + PelTag::YCBCR_POSITIONING, + PelTag::REFERENCE_BLACK_WHITE, + PelTag::COPYRIGHT, + PelTag::EXIF_IFD_POINTER, + PelTag::GPS_INFO_IFD_POINTER, + PelTag::PRINT_IM, + PelTag::XP_TITLE, + PelTag::XP_COMMENT, + PelTag::XP_AUTHOR, + PelTag::XP_KEYWORDS, + PelTag::XP_SUBJECT); + + case PelIfd::EXIF: + return array(PelTag::EXPOSURE_TIME, + PelTag::FNUMBER, + PelTag::EXPOSURE_PROGRAM, + PelTag::SPECTRAL_SENSITIVITY, + PelTag::ISO_SPEED_RATINGS, + PelTag::OECF, + PelTag::EXIF_VERSION, + PelTag::DATE_TIME_ORIGINAL, + PelTag::DATE_TIME_DIGITIZED, + PelTag::COMPONENTS_CONFIGURATION, + PelTag::COMPRESSED_BITS_PER_PIXEL, + PelTag::SHUTTER_SPEED_VALUE, + PelTag::APERTURE_VALUE, + PelTag::BRIGHTNESS_VALUE, + PelTag::EXPOSURE_BIAS_VALUE, + PelTag::MAX_APERTURE_VALUE, + PelTag::SUBJECT_DISTANCE, + PelTag::METERING_MODE, + PelTag::LIGHT_SOURCE, + PelTag::FLASH, + PelTag::FOCAL_LENGTH, + PelTag::MAKER_NOTE, + PelTag::USER_COMMENT, + PelTag::SUB_SEC_TIME, + PelTag::SUB_SEC_TIME_ORIGINAL, + PelTag::SUB_SEC_TIME_DIGITIZED, + PelTag::FLASH_PIX_VERSION, + PelTag::COLOR_SPACE, + PelTag::PIXEL_X_DIMENSION, + PelTag::PIXEL_Y_DIMENSION, + PelTag::RELATED_SOUND_FILE, + PelTag::FLASH_ENERGY, + PelTag::SPATIAL_FREQUENCY_RESPONSE, + PelTag::FOCAL_PLANE_X_RESOLUTION, + PelTag::FOCAL_PLANE_Y_RESOLUTION, + PelTag::FOCAL_PLANE_RESOLUTION_UNIT, + PelTag::SUBJECT_LOCATION, + PelTag::EXPOSURE_INDEX, + PelTag::SENSING_METHOD, + PelTag::FILE_SOURCE, + PelTag::SCENE_TYPE, + PelTag::CFA_PATTERN, + PelTag::CUSTOM_RENDERED, + PelTag::EXPOSURE_MODE, + PelTag::WHITE_BALANCE, + PelTag::DIGITAL_ZOOM_RATIO, + PelTag::FOCAL_LENGTH_IN_35MM_FILM, + PelTag::SCENE_CAPTURE_TYPE, + PelTag::GAIN_CONTROL, + PelTag::CONTRAST, + PelTag::SATURATION, + PelTag::SHARPNESS, + PelTag::DEVICE_SETTING_DESCRIPTION, + PelTag::SUBJECT_DISTANCE_RANGE, + PelTag::IMAGE_UNIQUE_ID, + PelTag::INTEROPERABILITY_IFD_POINTER, + PelTag::GAMMA); + + case PelIfd::GPS: + return array(PelTag::GPS_VERSION_ID, + PelTag::GPS_LATITUDE_REF, + PelTag::GPS_LATITUDE, + PelTag::GPS_LONGITUDE_REF, + PelTag::GPS_LONGITUDE, + PelTag::GPS_ALTITUDE_REF, + PelTag::GPS_ALTITUDE, + PelTag::GPS_TIME_STAMP, + PelTag::GPS_SATELLITES, + PelTag::GPS_STATUS, + PelTag::GPS_MEASURE_MODE, + PelTag::GPS_DOP, + PelTag::GPS_SPEED_REF, + PelTag::GPS_SPEED, + PelTag::GPS_TRACK_REF, + PelTag::GPS_TRACK, + PelTag::GPS_IMG_DIRECTION_REF, + PelTag::GPS_IMG_DIRECTION, + PelTag::GPS_MAP_DATUM, + PelTag::GPS_DEST_LATITUDE_REF, + PelTag::GPS_DEST_LATITUDE, + PelTag::GPS_DEST_LONGITUDE_REF, + PelTag::GPS_DEST_LONGITUDE, + PelTag::GPS_DEST_BEARING_REF, + PelTag::GPS_DEST_BEARING, + PelTag::GPS_DEST_DISTANCE_REF, + PelTag::GPS_DEST_DISTANCE, + PelTag::GPS_PROCESSING_METHOD, + PelTag::GPS_AREA_INFORMATION, + PelTag::GPS_DATE_STAMP, + PelTag::GPS_DIFFERENTIAL); + + case PelIfd::INTEROPERABILITY: + return array(PelTag::INTEROPERABILITY_INDEX, + PelTag::INTEROPERABILITY_VERSION, + PelTag::RELATED_IMAGE_FILE_FORMAT, + PelTag::RELATED_IMAGE_WIDTH, + PelTag::RELATED_IMAGE_LENGTH); + + /* TODO: Where do these tags belong? + PelTag::FILL_ORDER, + PelTag::DOCUMENT_NAME, + PelTag::TRANSFER_RANGE, + PelTag::JPEG_PROC, + PelTag::BATTERY_LEVEL, + PelTag::IPTC_NAA, + PelTag::INTER_COLOR_PROFILE, + PelTag::CFA_REPEAT_PATTERN_DIM, + */ + } + } + + + /** + * Get the name of an IFD type. + * + * @param int one of {@link PelIfd::IFD0}, {@link PelIfd::IFD1}, + * {@link PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link + * PelIfd::INTEROPERABILITY}. + * + * @return string the name of type. + */ + static function getTypeName($type) { + switch ($type) { + case self::IFD0: + return '0'; + case self::IFD1: + return '1'; + case self::EXIF: + return 'Exif'; + case self::GPS: + return 'GPS'; + case self::INTEROPERABILITY: + return 'Interoperability'; + default: + throw new PelIfdException('Unknown IFD type: %d', $type); + } + } + + + /** + * Get the name of this directory. + * + * @return string the name of this directory. + */ + function getName() { + return $this->getTypeName($this->type); + } + + + /** + * Adds an entry to the directory. + * + * @param PelEntry the entry that will be added. If the entry is not + * valid in this IFD (as per {@link isValidTag()}) an + * {@link PelInvalidDataException} is thrown. + * + * @todo The entry will be identified with its tag, so each + * directory can only contain one entry with each tag. Is this a + * bug? + */ + function addEntry(PelEntry $e) { + if ($this->isValidTag($e->getTag())) { + $e->setIfdType($this->type); + $this->entries[$e->getTag()] = $e; + } else { + throw new PelInvalidDataException("IFD %s cannot hold\n%s", + $this->getName(), $e->__toString()); + } + } + + + /** + * Does a given tag exist in this IFD? + * + * This methods is part of the ArrayAccess SPL interface for + * overriding array access of objects, it allows you to check for + * existance of an entry in the IFD: + * + * <code> + * if (isset($ifd[PelTag::FNUMBER])) + * // ... do something with the F-number. + * </code> + * + * @param PelTag the offset to check. + * + * @return boolean whether the tag exists. + */ + function offsetExists($tag) { + return isset($this->entries[$tag]); + } + + + /** + * Retrieve a given tag from this IFD. + * + * This methods is part of the ArrayAccess SPL interface for + * overriding array access of objects, it allows you to read entries + * from the IFD the same was as for an array: + * + * <code> + * $entry = $ifd[PelTag::FNUMBER]; + * </code> + * + * @param PelTag the tag to return. It is an error to ask for a tag + * which is not in the IFD, just like asking for a non-existant + * array entry. + * + * @return PelEntry the entry. + */ + function offsetGet($tag) { + return $this->entries[$tag]; + } + + + /** + * Set or update a given tag in this IFD. + * + * This methods is part of the ArrayAccess SPL interface for + * overriding array access of objects, it allows you to add new + * entries or replace esisting entries by doing: + * + * <code> + * $ifd[PelTag::EXPOSURE_BIAS_VALUE] = $entry; + * </code> + * + * Note that the actual array index passed is ignored! Instead the + * {@link PelTag} from the entry is used. + * + * @param PelTag the offset to update. + * + * @param PelEntry the new value. + */ + function offsetSet($tag, $e) { + if ($e instanceof PelEntry) { + $tag = $e->getTag(); + $this->entries[$tag] = $e; + } else { + throw new PelInvalidArgumentException('Argument "%s" must be a PelEntry.', $e); + } + } + + + /** + * Unset a given tag in this IFD. + * + * This methods is part of the ArrayAccess SPL interface for + * overriding array access of objects, it allows you to delete + * entries in the IFD by doing: + * + * <code> + * unset($ifd[PelTag::EXPOSURE_BIAS_VALUE]) + * </code> + * + * @param PelTag the offset to delete. + */ + function offsetUnset($tag) { + unset($this->entries[$tag]); + } + + + /** + * Retrieve an entry. + * + * @param PelTag the tag identifying the entry. + * + * @return PelEntry the entry associated with the tag, or null if no + * such entry exists. + */ + function getEntry($tag) { + if (isset($this->entries[$tag])) + return $this->entries[$tag]; + else + return null; + } + + + /** + * Returns all entries contained in this IFD. + * + * @return array an array of {@link PelEntry} objects, or rather + * descendant classes. The array has {@link PelTag}s as keys + * and the entries as values. + * + * @see getEntry + * @see getIterator + */ + function getEntries() { + return $this->entries; + } + + + /** + * Return an iterator for all entries contained in this IFD. + * + * Used with foreach as in + * + * <code> + * foreach ($ifd as $tag => $entry) { + * // $tag is now a PelTag and $entry is a PelEntry object. + * } + * </code> + * + * @return Iterator an iterator using the {@link PelTag tags} as + * keys and the entries as values. + */ + function getIterator() { + return new ArrayIterator($this->entries); + } + + + /** + * Returns available thumbnail data. + * + * @return string the bytes in the thumbnail, if any. If the IFD + * does not contain any thumbnail data, the empty string is + * returned. + * + * @todo Throw an exception instead when no data is available? + * + * @todo Return the $this->thumb_data object instead of the bytes? + */ + function getThumbnailData() { + if ($this->thumb_data != null) + return $this->thumb_data->getBytes(); + else + return ''; + } + + + /** + * Make this directory point to a new directory. + * + * @param PelIfd the IFD that this directory will point to. + */ + function setNextIfd(PelIfd $i) { + $this->next = $i; + } + + + /** + * Return the IFD pointed to by this directory. + * + * @return PelIfd the next IFD, following this IFD. If this is the + * last IFD, null is returned. + */ + function getNextIfd() { + return $this->next; + } + + + /** + * Check if this is the last IFD. + * + * @return boolean true if there are no following IFD, false + * otherwise. + */ + function isLastIfd() { + return $this->next == null; + } + + + /** + * Add a sub-IFD. + * + * Any previous sub-IFD of the same type will be overwritten. + * + * @param PelIfd the sub IFD. The type of must be one of {@link + * PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link + * PelIfd::INTEROPERABILITY}. + */ + function addSubIfd(PelIfd $sub) { + $this->sub[$sub->type] = $sub; + } + + + /** + * Return a sub IFD. + * + * @param int the type of the sub IFD. This must be one of {@link + * PelIfd::EXIF}, {@link PelIfd::GPS}, or {@link + * PelIfd::INTEROPERABILITY}. + * + * @return PelIfd the IFD associated with the type, or null if that + * sub IFD does not exist. + */ + function getSubIfd($type) { + if (isset($this->sub[$type])) + return $this->sub[$type]; + else + return null; + } + + + /** + * Get all sub IFDs. + * + * @return array an associative array with (IFD-type, {@link + * PelIfd}) pairs. + */ + function getSubIfds() { + return $this->sub; + } + + + /** + * Turn this directory into bytes. + * + * This directory will be turned into a byte string, with the + * specified byte order. The offsets will be calculated from the + * offset given. + * + * @param int the offset of the first byte of this directory. + * + * @param PelByteOrder the byte order that should be used when + * turning integers into bytes. This should be one of {@link + * PelConvert::LITTLE_ENDIAN} and {@link PelConvert::BIG_ENDIAN}. + */ + function getBytes($offset, $order) { + $bytes = ''; + $extra_bytes = ''; + + Pel::debug('Bytes from IDF will start at offset %d within Exif data', + $offset); + + $n = count($this->entries) + count($this->sub); + if ($this->thumb_data != null) { + /* We need two extra entries for the thumbnail offset and + * length. */ + $n += 2; + } + + $bytes .= PelConvert::shortToBytes($n, $order); + + /* Initialize offset of extra data. This included the bytes + * preceding this IFD, the bytes needed for the count of entries, + * the entries themselves (and sub entries), the extra data in the + * entries, and the IFD link. + */ + $end = $offset + 2 + 12 * $n + 4; + + foreach ($this->entries as $tag => $entry) { + /* Each entry is 12 bytes long. */ + $bytes .= PelConvert::shortToBytes($entry->getTag(), $order); + $bytes .= PelConvert::shortToBytes($entry->getFormat(), $order); + $bytes .= PelConvert::longToBytes($entry->getComponents(), $order); + + /* + * Size? If bigger than 4 bytes, the actual data is not in + * the entry but somewhere else. + */ + $data = $entry->getBytes($order); + $s = strlen($data); + if ($s > 4) { + Pel::debug('Data size %d too big, storing at offset %d instead.', + $s, $end); + $bytes .= PelConvert::longToBytes($end, $order); + $extra_bytes .= $data; + $end += $s; + } else { + Pel::debug('Data size %d fits.', $s); + /* Copy data directly, pad with NULL bytes as necessary to + * fill out the four bytes available.*/ + $bytes .= $data . str_repeat(chr(0), 4 - $s); + } + } + + if ($this->thumb_data != null) { + Pel::debug('Appending %d bytes of thumbnail data at %d', + $this->thumb_data->getSize(), $end); + // TODO: make PelEntry a class that can be constructed with + // arguments corresponding to the newt four lines. + $bytes .= PelConvert::shortToBytes(PelTag::JPEG_INTERCHANGE_FORMAT_LENGTH, + $order); + $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order); + $bytes .= PelConvert::longToBytes(1, $order); + $bytes .= PelConvert::longToBytes($this->thumb_data->getSize(), + $order); + + $bytes .= PelConvert::shortToBytes(PelTag::JPEG_INTERCHANGE_FORMAT, + $order); + $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order); + $bytes .= PelConvert::longToBytes(1, $order); + $bytes .= PelConvert::longToBytes($end, $order); + + $extra_bytes .= $this->thumb_data->getBytes(); + $end += $this->thumb_data->getSize(); + } + + + /* Find bytes from sub IFDs. */ + $sub_bytes = ''; + foreach ($this->sub as $type => $sub) { + if ($type == PelIfd::EXIF) + $tag = PelTag::EXIF_IFD_POINTER; + elseif ($type == PelIfd::GPS) + $tag = PelTag::GPS_INFO_IFD_POINTER; + elseif ($type == PelIfd::INTEROPERABILITY) + $tag = PelTag::INTEROPERABILITY_IFD_POINTER; + + /* Make an aditional entry with the pointer. */ + $bytes .= PelConvert::shortToBytes($tag, $order); + /* Next the format, which is always unsigned long. */ + $bytes .= PelConvert::shortToBytes(PelFormat::LONG, $order); + /* There is only one component. */ + $bytes .= PelConvert::longToBytes(1, $order); + + $data = $sub->getBytes($end, $order); + $s = strlen($data); + $sub_bytes .= $data; + + $bytes .= PelConvert::longToBytes($end, $order); + $end += $s; + } + + /* Make link to next IFD, if any*/ + if ($this->isLastIFD()) { + $link = 0; + } else { + $link = $end; + } + + Pel::debug('Link to next IFD: %d', $link); + + $bytes .= PelConvert::longtoBytes($link, $order); + + $bytes .= $extra_bytes . $sub_bytes; + + if (!$this->isLastIfd()) + $bytes .= $this->next->getBytes($end, $order); + + return $bytes; + } + + + /** + * Turn this directory into text. + * + * @return string information about the directory, mainly for + * debugging. + */ + function __toString() { + $str = Pel::fmt("Dumping IFD %s with %d entries...\n", + $this->getName(), count($this->entries)); + + foreach ($this->entries as $entry) + $str .= $entry->__toString(); + + $str .= Pel::fmt("Dumping %d sub IFDs...\n", count($this->sub)); + + foreach ($this->sub as $type => $ifd) + $str .= $ifd->__toString(); + + if ($this->next != null) + $str .= $this->next->__toString(); + + return $str; + } + + +} + diff --git a/modules/autorotate/lib/pel/PelJpeg.php b/modules/autorotate/lib/pel/PelJpeg.php new file mode 100644 index 0000000..8a39c40 --- /dev/null +++ b/modules/autorotate/lib/pel/PelJpeg.php @@ -0,0 +1,609 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006, 2007 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes representing JPEG data. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelJpegComment.php'); +require_once('PelJpegContent.php'); +require_once('PelDataWindow.php'); +require_once('PelJpegMarker.php'); +require_once('PelException.php'); +require_once('PelExif.php'); +require_once('Pel.php'); +/**#@-*/ + + +/** + * Exception thrown when an invalid marker is found. + * + * This exception is thrown when PEL expects to find a {@link + * PelJpegMarker} and instead finds a byte that isn't a known marker. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + * @subpackage Exception + */ +class PelJpegInvalidMarkerException extends PelException { + + /** + * Construct a new invalid marker exception. + * + * The exception will contain a message describing the error, + * including the byte found and the offset of the offending byte. + * + * @param int the byte found. + * + * @param int the offset in the data. + */ + function __construct($marker, $offset) { + parent::__construct('Invalid marker found at offset %d: 0x%2X', + $offset, $marker); + } +} + +/** + * Class for handling JPEG data. + * + * The {@link PelJpeg} class defined here provides an abstraction for + * dealing with a JPEG file. The file will be contain a number of + * sections containing some {@link PelJpegContent content} identified + * by a {@link PelJpegMarker marker}. + * + * The {@link getExif()} method is used get hold of the {@link + * PelJpegMarker::APP1 APP1} section which stores Exif data. So if + * the name of the JPEG file is stored in $filename, then one would + * get hold of the Exif data by saying: + * + * <code> + * $jpeg = new PelJpeg($filename); + * $exif = $jpeg->getExif(); + * $tiff = $exif->getTiff(); + * $ifd0 = $tiff->getIfd(); + * $exif = $ifd0->getSubIfd(PelIfd::EXIF); + * $ifd1 = $ifd0->getNextIfd(); + * </code> + * + * The $idf0 and $ifd1 variables will then be two {@link PelTiff TIFF} + * {@link PelIfd Image File Directories}, in which the data is stored + * under the keys found in {@link PelTag}. + * + * Should one have some image data (in the form of a {@link + * PelDataWindow}) of an unknown type, then the {@link + * PelJpeg::isValid()} function is handy: it will quickly test if the + * data could be valid JPEG data. The {@link PelTiff::isValid()} + * function does the same for TIFF images. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelJpeg { + + /** + * The sections in the JPEG data. + * + * A JPEG file is built up as a sequence of sections, each section + * is identified with a {@link PelJpegMarker}. Some sections can + * occur more than once in the JPEG stream (the {@link + * PelJpegMarker::DQT DQT} and {@link PelJpegMarker::DHT DTH} + * markers for example) and so this is an array of ({@link + * PelJpegMarker}, {@link PelJpegContent}) pairs. + * + * The content can be either generic {@link PelJpegContent JPEG + * content} or {@link PelExif Exif data}. + * + * @var array + */ + private $sections = array(); + + /** + * The JPEG image data. + * + * @var PelDataWindow + */ + private $jpeg_data = null; + + /** + * Construct a new JPEG object. + * + * The new object will be empty unless an argument is given from + * which it can initialize itself. This can either be the filename + * of a JPEG image, a {@link PelDataWindow} object or a PHP image + * resource handle. + * + * New Exif data (in the form of a {@link PelExif} object) can be + * inserted with the {@link setExif()} method: + * + * <code> + * $jpeg = new PelJpeg($data); + * // Create container for the Exif information: + * $exif = new PelExif(); + * // Now Add a PelTiff object with a PelIfd object with one or more + * // PelEntry objects to $exif... Finally add $exif to $jpeg: + * $jpeg->setExif($exif); + * </code> + * + * @param mixed the data that this JPEG. This can either be a + * filename, a {@link PelDataWindow} object, or a PHP image resource + * handle. + */ + function __construct($data = false) { + if ($data === false) + return; + + if (is_string($data)) { + Pel::debug('Initializing PelJpeg object from %s', $data); + $this->loadFile($data); + } elseif ($data instanceof PelDataWindow) { + Pel::debug('Initializing PelJpeg object from PelDataWindow.'); + $this->load($data); + } elseif (is_resource($data) && get_resource_type($data) == 'gd') { + Pel::debug('Initializing PelJpeg object from image resource.'); + $this->load(new PelDataWindow($data)); + } else { + throw new PelInvalidArgumentException('Bad type for $data: %s', + gettype($data)); + } + } + + /** + * Load data into a JPEG object. + * + * The data supplied will be parsed and turned into an object + * structure representing the image. This structure can then be + * manipulated and later turned back into an string of bytes. + * + * This methods can be called at any time after a JPEG object has + * been constructed, also after the {@link appendSection()} has been + * called to append custom sections. Loading several JPEG images + * into one object will accumulate the sections, but there will only + * be one {@link PelJpegMarker::SOS} section at any given time. + * + * @param PelDataWindow the data that will be turned into JPEG + * sections. + */ + function load(PelDataWindow $d) { + + Pel::debug('Parsing %d bytes...', $d->getSize()); + + /* JPEG data is stored in big-endian format. */ + $d->setByteOrder(PelConvert::BIG_ENDIAN); + + /* Run through the data to read the sections in the image. After + * each section is read, the start of the data window will be + * moved forward, and after the last section we'll terminate with + * no data left in the window. */ + while ($d->getSize() > 0) { + /* JPEG sections start with 0xFF. The first byte that is not + * 0xFF is a marker (hopefully). + */ + for ($i = 0; $i < 7; $i++) + if ($d->getByte($i) != 0xFF) + break; + + $marker = $d->getByte($i); + + if (!PelJpegMarker::isValid($marker)) + throw new PelJpegInvalidMarkerException($marker, $i); + + /* Move window so first byte becomes first byte in this + * section. */ + $d->setWindowStart($i+1); + + if ($marker == PelJpegMarker::SOI || $marker == PelJpegMarker::EOI) { + $content = new PelJpegContent(new PelDataWindow()); + $this->appendSection($marker, $content); + } else { + /* Read the length of the section. The length includes the + * two bytes used to store the length. */ + $len = $d->getShort(0) - 2; + + Pel::debug('Found %s section of length %d', + PelJpegMarker::getName($marker), $len); + + /* Skip past the length. */ + $d->setWindowStart(2); + + if ($marker == PelJpegMarker::APP1) { + + try { + $content = new PelExif(); + $content->load($d->getClone(0, $len)); + } catch (PelInvalidDataException $e) { + /* We store the data as normal JPEG content if it could + * not be parsed as Exif data. */ + $content = new PelJpegContent($d->getClone(0, $len)); + } + + $this->appendSection($marker, $content); + /* Skip past the data. */ + $d->setWindowStart($len); + + } elseif ($marker == PelJpegMarker::COM) { + + $content = new PelJpegComment(); + $content->load($d->getClone(0, $len)); + $this->appendSection($marker, $content); + $d->setWindowStart($len); + + } else { + + $content = new PelJpegContent($d->getClone(0, $len)); + $this->appendSection($marker, $content); + /* Skip past the data. */ + $d->setWindowStart($len); + + /* In case of SOS, image data will follow. */ + if ($marker == PelJpegMarker::SOS) { + /* Some images have some trailing (garbage?) following the + * EOI marker. To handle this we seek backwards until we + * find the EOI marker. Any trailing content is stored as + * a PelJpegContent object. */ + + $length = $d->getSize(); + while ($d->getByte($length-2) != 0xFF || + $d->getByte($length-1) != PelJpegMarker::EOI) { + $length--; + } + + $this->jpeg_data = $d->getClone(0, $length-2); + Pel::debug('JPEG data: ' . $this->jpeg_data->__toString()); + + /* Append the EOI. */ + $this->appendSection(PelJpegMarker::EOI, + new PelJpegContent(new PelDataWindow())); + + /* Now check to see if there are any trailing data. */ + if ($length != $d->getSize()) { + Pel::maybeThrow(new PelException('Found trailing content ' . + 'after EOI: %d bytes', + $d->getSize() - $length)); + $content = new PelJpegContent($d->getClone($length)); + /* We don't have a proper JPEG marker for trailing + * garbage, so we just use 0x00... */ + $this->appendSection(0x00, $content); + } + + /* Done with the loop. */ + break; + } + } + } + } /* while ($d->getSize() > 0) */ + } + + + /** + * Load data from a file into a JPEG object. + * + * @param string the filename. This must be a readable file. + */ + function loadFile($filename) { + $this->load(new PelDataWindow(file_get_contents($filename))); + } + + + /** + * Set Exif data. + * + * Use this to set the Exif data in the image. This will overwrite + * any old Exif information in the image. + * + * @param PelExif the Exif data. + */ + function setExif(PelExif $exif) { + $app0_offset = 1; + $app1_offset = -1; + + /* Search through all sections looking for APP0 or APP1. */ + for ($i = 0; $i < count($this->sections); $i++) { + if ($this->sections[$i][0] == PelJpegMarker::APP0) { + $app0_offset = $i; + } elseif ($this->sections[$i][0] == PelJpegMarker::APP1) { + $app1_offset = $i; + break; + } + } + + /* Store the Exif data at the appropriate place, either where the + * old Exif data was stored ($app1_offset) or right after APP0 + * ($app0_offset+1). */ + if ($app1_offset > 0) + $this->sections[$app1_offset][1] = $exif; + else + $this->insertSection(PelJpegMarker::APP1, $exif, $app0_offset+1); + } + + + /** + * Get Exif data. + * + * Use this to get the @{link PelExif Exif data} stored. + * + * @return PelExif the Exif data found or null if the image has no + * Exif data. + */ + function getExif() { + $exif = $this->getSection(PelJpegMarker::APP1); + if ($exif instanceof PelExif) + return $exif; + else + return null; + } + + + /** + * Clear any Exif data. + * + * This method will only clear the first @{link PelJpegMarker::APP1} + * section found (there should normally be just one). + */ + function clearExif() { + for ($i = 0; $i < count($this->sections); $i++) { + if ($this->sections[$i][0] == PelJpegMarker::APP1) { + unset($this->sections[$i]); + return; + } + } + } + + + /** + * Append a new section. + * + * Used only when loading an image. If it used again later, then the + * section will end up after the @{link PelJpegMarker::EOI EOI + * marker} and will probably not be useful. + * + * Please use @{link setExif()} instead if you intend to add Exif + * information to an image as that function will know the right + * place to insert the data. + * + * @param PelJpegMarker the marker identifying the new section. + * + * @param PelJpegContent the content of the new section. + */ + function appendSection($marker, PelJpegContent $content) { + $this->sections[] = array($marker, $content); + } + + + /** + * Insert a new section. + * + * Please use @{link setExif()} instead if you intend to add Exif + * information to an image as that function will know the right + * place to insert the data. + * + * @param PelJpegMarker the marker for the new section. + * + * @param PelJpegContent the content of the new section. + * + * @param int the offset where the new section will be inserted --- + * use 0 to insert it at the very beginning, use 1 to insert it + * between sections 1 and 2, etc. + */ + function insertSection($marker, PelJpegContent $content, $offset) { + array_splice($this->sections, $offset, 0, array(array($marker, $content))); + } + + + /** + * Get a section corresponding to a particular marker. + * + * Please use the {@link getExif()} if you just need the Exif data. + * + * This will search through the sections of this JPEG object, + * looking for a section identified with the specified {@link + * PelJpegMarker marker}. The {@link PelJpegContent content} will + * then be returned. The optional argument can be used to skip over + * some of the sections. So if one is looking for the, say, third + * {@link PelJpegMarker::DHT DHT} section one would do: + * + * <code> + * $dht3 = $jpeg->getSection(PelJpegMarker::DHT, 2); + * </code> + * + * @param PelJpegMarker the marker identifying the section. + * + * @param int the number of sections to be skipped. This must be a + * non-negative integer. + * + * @return PelJpegContent the content found, or null if there is no + * content available. + */ + function getSection($marker, $skip = 0) { + foreach ($this->sections as $s) { + if ($s[0] == $marker) + if ($skip > 0) + $skip--; + else + return $s[1]; + } + + return null; + } + + + /** + * Get all sections. + * + * @return array an array of ({@link PelJpegMarker}, {@link + * PelJpegContent}) pairs. Each pair is an array with the {@link + * PelJpegMarker} as the first element and the {@link + * PelJpegContent} as the second element, so the return type is an + * array of arrays. + * + * So to loop through all the sections in a given JPEG image do + * this: + * + * <code> + * foreach ($jpeg->getSections() as $section) { + * $marker = $section[0]; + * $content = $section[1]; + * // Use $marker and $content here. + * } + * </code> + * + * instead of this: + * + * <code> + * foreach ($jpeg->getSections() as $marker => $content) { + * // Does not work the way you would think... + * } + * </code> + * + * The problem is that there could be several sections with the same + * marker, and thus a simple associative array does not suffice. + */ + function getSections() { + return $this->sections; + } + + + /** + * Turn this JPEG object into bytes. + * + * The bytes returned by this method is ready to be stored in a file + * as a valid JPEG image. Use the {@link saveFile()} convenience + * method to do this. + * + * @return string bytes representing this JPEG object, including all + * its sections and their associated data. + */ + function getBytes() { + $bytes = ''; + + foreach ($this->sections as $section) { + $m = $section[0]; + $c = $section[1]; + + /* Write the marker */ + $bytes .= "\xFF" . PelJpegMarker::getBytes($m); + /* Skip over empty markers. */ + if ($m == PelJpegMarker::SOI || $m == PelJpegMarker::EOI) + continue; + + $data = $c->getBytes(); + $size = strlen($data) + 2; + + $bytes .= PelConvert::shortToBytes($size, PelConvert::BIG_ENDIAN); + $bytes .= $data; + + /* In case of SOS, we need to write the JPEG data. */ + if ($m == PelJpegMarker::SOS) + $bytes .= $this->jpeg_data->getBytes(); + } + + return $bytes; + + } + + + /** + * Save the JPEG object as a JPEG image in a file. + * + * @param string the filename to save in. An existing file with the + * same name will be overwritten! + */ + function saveFile($filename) { + file_put_contents($filename, $this->getBytes()); + } + + + /** + * Make a string representation of this JPEG object. + * + * This is mainly usefull for debugging. It will show the structure + * of the image, and its sections. + * + * @return string debugging information about this JPEG object. + */ + function __toString() { + $str = Pel::tra("Dumping JPEG data...\n"); + for ($i = 0; $i < count($this->sections); $i++) { + $m = $this->sections[$i][0]; + $c = $this->sections[$i][1]; + $str .= Pel::fmt("Section %d (marker 0x%02X - %s):\n", + $i, $m, PelJpegMarker::getName($m)); + $str .= Pel::fmt(" Description: %s\n", + PelJpegMarker::getDescription($m)); + + if ($m == PelJpegMarker::SOI || + $m == PelJpegMarker::EOI) + continue; + + if ($c instanceof PelExif) { + $str .= Pel::tra(" Content : Exif data\n"); + $str .= $c->__toString() . "\n"; + } elseif ($c instanceof PelJpegComment) { + $str .= Pel::fmt(" Content : %s\n", $c->getValue()); + } else { + $str .= Pel::tra(" Content : Unknown\n"); + } + } + + return $str; + } + + + /** + * Test data to see if it could be a valid JPEG image. + * + * The function will only look at the first few bytes of the data, + * and try to determine if it could be a valid JPEG image based on + * those bytes. This means that the check is more like a heuristic + * than a rigorous check. + * + * @param PelDataWindow the bytes that will be checked. + * + * @return boolean true if the bytes look like the beginning of a + * JPEG image, false otherwise. + * + * @see PelTiff::isValid() + */ + static function isValid(PelDataWindow $d) { + /* JPEG data is stored in big-endian format. */ + $d->setByteOrder(PelConvert::BIG_ENDIAN); + + for ($i = 0; $i < 7; $i++) + if ($d->getByte($i) != 0xFF) + break; + + return $d->getByte($i) == PelJpegMarker::SOI; + } + +} + diff --git a/modules/autorotate/lib/pel/PelJpegComment.php b/modules/autorotate/lib/pel/PelJpegComment.php new file mode 100644 index 0000000..6c3308e --- /dev/null +++ b/modules/autorotate/lib/pel/PelJpegComment.php @@ -0,0 +1,121 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2005, 2007 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Class for dealing with JPEG comments. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelJpegContent.php'); +/**#@-*/ + + +/** + * Class representing JPEG comments. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelJpegComment extends PelJpegContent { + + /** + * The comment. + * + * @var string + */ + private $comment = ''; + + /** + * Construct a new JPEG comment. + * + * The new comment will contain the string given. + */ + function __construct($comment = '') { + $this->comment = $comment; + } + + + /** + * Load and parse data. + * + * This will load the comment from the data window passed. + */ + function load(PelDataWindow $d) { + $this->comment = $d->getBytes(); + } + + + /** + * Update the value with a new comment. + * + * Any old comment will be overwritten. + * + * @param string the new comment. + */ + function setValue($comment) { + $this->comment = $comment; + } + + + /** + * Get the comment. + * + * @return string the comment. + */ + function getValue() { + return $this->comment; + } + + + /** + * Turn this comment into bytes. + * + * @return string bytes representing this comment. + */ + function getBytes() { + return $this->comment; + } + + + /** + * Return a string representation of this object. + * + * @return string the same as {@link getValue()}. + */ + function __toString() { + return $this->getValue(); + } + +} + diff --git a/modules/autorotate/lib/pel/PelJpegContent.php b/modules/autorotate/lib/pel/PelJpegContent.php new file mode 100644 index 0000000..42dd79d --- /dev/null +++ b/modules/autorotate/lib/pel/PelJpegContent.php @@ -0,0 +1,82 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Class representing content in a JPEG file. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelDataWindow.php'); +/**#@-*/ + + +/** + * Class representing content in a JPEG file. + * + * A JPEG file consists of a sequence of each of which has an + * associated {@link PelJpegMarker marker} and some content. This + * class represents the content, and this basic type is just a simple + * holder of such content, represented by a {@link PelDataWindow} + * object. The {@link PelExif} class is an example of more + * specialized JPEG content. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelJpegContent { + private $data = null; + + /** + * Make a new piece of JPEG content. + * + * @param PelDataWindow the content. + */ + function __construct(PelDataWindow $data) { + $this->data = $data; + } + + + /** + * Return the bytes of the content. + * + * @return string bytes representing this JPEG content. These bytes + * will match the bytes given to {@link __construct the + * constructor}. + */ + function getBytes() { + return $this->data->getBytes(); + } + +} + diff --git a/modules/autorotate/lib/pel/PelJpegMarker.php b/modules/autorotate/lib/pel/PelJpegMarker.php new file mode 100644 index 0000000..f957c0a --- /dev/null +++ b/modules/autorotate/lib/pel/PelJpegMarker.php @@ -0,0 +1,435 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes for dealing with JPEG markers. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('Pel.php'); +/**#@-*/ + + +/** + * Class with static methods for JPEG markers. + * + * This class defines the constants to be used whenever one refers to + * a JPEG marker. All the methods defined are static, and they all + * operate on one argument which should be one of the class constants. + * They will all be denoted by PelJpegMarker in the documentation. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelJpegMarker { + + /** Encoding (baseline) */ + const SOF0 = 0xC0; + /** Encoding (extended sequential) */ + const SOF1 = 0xC1; + /** Encoding (progressive) */ + const SOF2 = 0xC2; + /** Encoding (lossless) */ + const SOF3 = 0xC3; + /** Define Huffman table */ + const DHT = 0xC4; + /** Encoding (differential sequential) */ + const SOF5 = 0xC5; + /** Encoding (differential progressive) */ + const SOF6 = 0xC6; + /** Encoding (differential lossless) */ + const SOF7 = 0xC7; + /** Extension */ + const JPG = 0xC8; + /** Encoding (extended sequential, arithmetic) */ + const SOF9 = 0xC9; + /** Encoding (progressive, arithmetic) */ + const SOF10 = 0xCA; + /** Encoding (lossless, arithmetic) */ + const SOF11 = 0xCB; + /** Define arithmetic coding conditioning */ + const DAC = 0xCC; + /** Encoding (differential sequential, arithmetic) */ + const SOF13 = 0xCD; + /** Encoding (differential progressive, arithmetic) */ + const SOF14 = 0xCE; + /** Encoding (differential lossless, arithmetic) */ + const SOF15 = 0xCF; + /** Restart 0 */ + const RST0 = 0xD0; + /** Restart 1 */ + const RST1 = 0xD1; + /** Restart 2 */ + const RST2 = 0xD2; + /** Restart 3 */ + const RST3 = 0xD3; + /** Restart 4 */ + const RST4 = 0xD4; + /** Restart 5 */ + const RST5 = 0xD5; + /** Restart 6 */ + const RST6 = 0xD6; + /** Restart 7 */ + const RST7 = 0xD7; + /** Start of image */ + const SOI = 0xD8; + /** End of image */ + const EOI = 0xD9; + /** Start of scan */ + const SOS = 0xDA; + /** Define quantization table */ + const DQT = 0xDB; + /** Define number of lines */ + const DNL = 0xDC; + /** Define restart interval */ + const DRI = 0xDD; + /** Define hierarchical progression */ + const DHP = 0xDE; + /** Expand reference component */ + const EXP = 0xDF; + /** Application segment 0 */ + const APP0 = 0xE0; + /** + * Application segment 1 + * + * When a JPEG image contains Exif data, the data will normally be + * stored in this section and a call to {@link PelJpeg::getExif()} + * will return a {@link PelExif} object representing it. + */ + const APP1 = 0xE1; + /** Application segment 2 */ + const APP2 = 0xE2; + /** Application segment 3 */ + const APP3 = 0xE3; + /** Application segment 4 */ + const APP4 = 0xE4; + /** Application segment 5 */ + const APP5 = 0xE5; + /** Application segment 6 */ + const APP6 = 0xE6; + /** Application segment 7 */ + const APP7 = 0xE7; + /** Application segment 8 */ + const APP8 = 0xE8; + /** Application segment 9 */ + const APP9 = 0xE9; + /** Application segment 10 */ + const APP10 = 0xEA; + /** Application segment 11 */ + const APP11 = 0xEB; + /** Application segment 12 */ + const APP12 = 0xEC; + /** Application segment 13 */ + const APP13 = 0xED; + /** Application segment 14 */ + const APP14 = 0xEE; + /** Application segment 15 */ + const APP15 = 0xEF; + /** Extension 0 */ + const JPG0 = 0xF0; + /** Extension 1 */ + const JPG1 = 0xF1; + /** Extension 2 */ + const JPG2 = 0xF2; + /** Extension 3 */ + const JPG3 = 0xF3; + /** Extension 4 */ + const JPG4 = 0xF4; + /** Extension 5 */ + const JPG5 = 0xF5; + /** Extension 6 */ + const JPG6 = 0xF6; + /** Extension 7 */ + const JPG7 = 0xF7; + /** Extension 8 */ + const JPG8 = 0xF8; + /** Extension 9 */ + const JPG9 = 0xF9; + /** Extension 10 */ + const JPG10 = 0xFA; + /** Extension 11 */ + const JPG11 = 0xFB; + /** Extension 12 */ + const JPG12 = 0xFC; + /** Extension 13 */ + const JPG13 = 0xFD; + /** Comment */ + const COM = 0xFE; + + /** + * Check if a byte is a valid JPEG marker. + * + * @param PelJpegMarker the byte that will be checked. + * + * @return boolean if the byte is recognized true is returned, + * otherwise false will be returned. + */ + static function isValid($m) { + return ($m >= self::SOF0 && $m <= self::COM); + } + + /** + * Turn a JPEG marker into bytes. + * + * @param PelJpegMarker the marker. + * + * @return string the marker as a string. This will be a string + * with just a single byte since all JPEG markers are simply single + * bytes. + */ + static function getBytes($m) { + return chr($m); + } + + /** + * Return the short name for a marker. + * + * @param PelJpegMarker the marker. + * + * @return string the name of the marker, e.g., 'SOI' for the Start + * of Image marker. + */ + static function getName($m) { + switch ($m) { + case self::SOF0: return 'SOF0'; + case self::SOF1: return 'SOF1'; + case self::SOF2: return 'SOF2'; + case self::SOF3: return 'SOF3'; + case self::SOF5: return 'SOF5'; + case self::SOF6: return 'SOF6'; + case self::SOF7: return 'SOF7'; + case self::SOF9: return 'SOF9'; + case self::SOF10: return 'SOF10'; + case self::SOF11: return 'SOF11'; + case self::SOF13: return 'SOF13'; + case self::SOF14: return 'SOF14'; + case self::SOF15: return 'SOF15'; + case self::SOI: return 'SOI'; + case self::EOI: return 'EOI'; + case self::SOS: return 'SOS'; + case self::COM: return 'COM'; + case self::DHT: return 'DHT'; + case self::JPG: return 'JPG'; + case self::DAC: return 'DAC'; + case self::RST0: return 'RST0'; + case self::RST1: return 'RST1'; + case self::RST2: return 'RST2'; + case self::RST3: return 'RST3'; + case self::RST4: return 'RST4'; + case self::RST5: return 'RST5'; + case self::RST6: return 'RST6'; + case self::RST7: return 'RST7'; + case self::DQT: return 'DQT'; + case self::DNL: return 'DNL'; + case self::DRI: return 'DRI'; + case self::DHP: return 'DHP'; + case self::EXP: return 'EXP'; + case self::APP0: return 'APP0'; + case self::APP1: return 'APP1'; + case self::APP2: return 'APP2'; + case self::APP3: return 'APP3'; + case self::APP4: return 'APP4'; + case self::APP5: return 'APP5'; + case self::APP6: return 'APP6'; + case self::APP7: return 'APP7'; + case self::APP8: return 'APP8'; + case self::APP9: return 'APP9'; + case self::APP10: return 'APP10'; + case self::APP11: return 'APP11'; + case self::APP12: return 'APP12'; + case self::APP13: return 'APP13'; + case self::APP14: return 'APP14'; + case self::APP15: return 'APP15'; + case self::JPG0: return 'JPG0'; + case self::JPG1: return 'JPG1'; + case self::JPG2: return 'JPG2'; + case self::JPG3: return 'JPG3'; + case self::JPG4: return 'JPG4'; + case self::JPG5: return 'JPG5'; + case self::JPG6: return 'JPG6'; + case self::JPG7: return 'JPG7'; + case self::JPG8: return 'JPG8'; + case self::JPG9: return 'JPG9'; + case self::JPG10: return 'JPG10'; + case self::JPG11: return 'JPG11'; + case self::JPG12: return 'JPG12'; + case self::JPG13: return 'JPG13'; + case self::COM: return 'COM'; + default: return Pel::fmt('Unknown marker: 0x%02X', $m); + } + } + + /** + * Returns a description of a JPEG marker. + * + * @param PelJpegMarker the marker. + * + * @return string the description of the marker. + */ + static function getDescription($m) { + switch ($m) { + case self::SOF0: + return Pel::tra('Encoding (baseline)'); + case self::SOF1: + return Pel::tra('Encoding (extended sequential)'); + case self::SOF2: + return Pel::tra('Encoding (progressive)'); + case self::SOF3: + return Pel::tra('Encoding (lossless)'); + case self::SOF5: + return Pel::tra('Encoding (differential sequential)'); + case self::SOF6: + return Pel::tra('Encoding (differential progressive)'); + case self::SOF7: + return Pel::tra('Encoding (differential lossless)'); + case self::SOF9: + return Pel::tra('Encoding (extended sequential, arithmetic)'); + case self::SOF10: + return Pel::tra('Encoding (progressive, arithmetic)'); + case self::SOF11: + return Pel::tra('Encoding (lossless, arithmetic)'); + case self::SOF13: + return Pel::tra('Encoding (differential sequential, arithmetic)'); + case self::SOF14: + return Pel::tra('Encoding (differential progressive, arithmetic)'); + case self::SOF15: + return Pel::tra('Encoding (differential lossless, arithmetic)'); + case self::SOI: + return Pel::tra('Start of image'); + case self::EOI: + return Pel::tra('End of image'); + case self::SOS: + return Pel::tra('Start of scan'); + case self::COM: + return Pel::tra('Comment'); + case self::DHT: + return Pel::tra('Define Huffman table'); + case self::JPG: + return Pel::tra('Extension'); + case self::DAC: + return Pel::tra('Define arithmetic coding conditioning'); + case self::RST0: + return Pel::fmt('Restart %d', 0); + case self::RST1: + return Pel::fmt('Restart %d', 1); + case self::RST2: + return Pel::fmt('Restart %d', 2); + case self::RST3: + return Pel::fmt('Restart %d', 3); + case self::RST4: + return Pel::fmt('Restart %d', 4); + case self::RST5: + return Pel::fmt('Restart %d', 5); + case self::RST6: + return Pel::fmt('Restart %d', 6); + case self::RST7: + return Pel::fmt('Restart %d', 7); + case self::DQT: + return Pel::tra('Define quantization table'); + case self::DNL: + return Pel::tra('Define number of lines'); + case self::DRI: + return Pel::tra('Define restart interval'); + case self::DHP: + return Pel::tra('Define hierarchical progression'); + case self::EXP: + return Pel::tra('Expand reference component'); + case self::APP0: + return Pel::fmt('Application segment %d', 0); + case self::APP1: + return Pel::fmt('Application segment %d', 1); + case self::APP2: + return Pel::fmt('Application segment %d', 2); + case self::APP3: + return Pel::fmt('Application segment %d', 3); + case self::APP4: + return Pel::fmt('Application segment %d', 4); + case self::APP5: + return Pel::fmt('Application segment %d', 5); + case self::APP6: + return Pel::fmt('Application segment %d', 6); + case self::APP7: + return Pel::fmt('Application segment %d', 7); + case self::APP8: + return Pel::fmt('Application segment %d', 8); + case self::APP9: + return Pel::fmt('Application segment %d', 9); + case self::APP10: + return Pel::fmt('Application segment %d', 10); + case self::APP11: + return Pel::fmt('Application segment %d', 11); + case self::APP12: + return Pel::fmt('Application segment %d', 12); + case self::APP13: + return Pel::fmt('Application segment %d', 13); + case self::APP14: + return Pel::fmt('Application segment %d', 14); + case self::APP15: + return Pel::fmt('Application segment %d', 15); + case self::JPG0: + return Pel::fmt('Extension %d', 0); + case self::JPG1: + return Pel::fmt('Extension %d', 1); + case self::JPG2: + return Pel::fmt('Extension %d', 2); + case self::JPG3: + return Pel::fmt('Extension %d', 3); + case self::JPG4: + return Pel::fmt('Extension %d', 4); + case self::JPG5: + return Pel::fmt('Extension %d', 5); + case self::JPG6: + return Pel::fmt('Extension %d', 6); + case self::JPG7: + return Pel::fmt('Extension %d', 7); + case self::JPG8: + return Pel::fmt('Extension %d', 8); + case self::JPG9: + return Pel::fmt('Extension %d', 9); + case self::JPG10: + return Pel::fmt('Extension %d', 10); + case self::JPG11: + return Pel::fmt('Extension %d', 11); + case self::JPG12: + return Pel::fmt('Extension %d', 12); + case self::JPG13: + return Pel::fmt('Extension %d', 13); + case self::COM: + return Pel::tra('Comment'); + default: + return Pel::fmt('Unknown marker: 0x%02X', $m); + } + } +} + diff --git a/modules/autorotate/lib/pel/PelTag.php b/modules/autorotate/lib/pel/PelTag.php new file mode 100644 index 0000000..ce9bd84 --- /dev/null +++ b/modules/autorotate/lib/pel/PelTag.php @@ -0,0 +1,1975 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006, 2007 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Namespace for functions operating on Exif tags. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('Pel.php'); +require_once('PelIfd.php'); +/**#@-*/ + + +/** + * Class with static methods for Exif tags. + * + * This class defines the constants that represents the Exif tags + * known to PEL. They are supposed to be used whenever one needs to + * specify an Exif tag, and they will be denoted by the pseudo-type + * {@link PelTag} throughout the documentation. + * + * Please note that the constrains on the format and number of + * components given here are advisory only. To follow the Exif + * specification one should obey them, but there is nothing that + * prevents you from creating an {@link IMAGE_LENGTH} entry with two + * or more components, even though the standard says that there should + * be exactly one component. + * + * All the methods in this class are static and should be called with + * the Exif tag on which they should operate. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelTag { + + /** + * Interoperability index. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 4. + */ + const INTEROPERABILITY_INDEX = 0x0001; + + /** + * Interoperability version. + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: 4. + */ + const INTEROPERABILITY_VERSION = 0x0002; + + /** + * Image width. + * + * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}. + * + * Components: 1. + */ + const IMAGE_WIDTH = 0x0100; + + /** + * Image length. + * + * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}. + * + * Components: 1. + */ + const IMAGE_LENGTH = 0x0101; + + /** + * Number of bits per component. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 3. + */ + const BITS_PER_SAMPLE = 0x0102; + + /** + * Compression scheme. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const COMPRESSION = 0x0103; + + /** + * Pixel composition. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const PHOTOMETRIC_INTERPRETATION = 0x0106; + + /** + * Fill Orde + * + * Format: Unknown. + * + * Components: Unknown. + */ + const FILL_ORDER = 0x010A; + + /** + * Document Name + * + * Format: Unknown. + * + * Components: Unknown. + */ + const DOCUMENT_NAME = 0x010D; + + /** + * Image Description + * + * Format: {@link PelEntryAscii}. + * + * Components: any number. + */ + const IMAGE_DESCRIPTION = 0x010E; + + /** + * Manufacturer + * + * Format: {@link PelEntryAscii}. + * + * Components: any number. + */ + const MAKE = 0x010F; + + /** + * Model + * + * Format: {@link PelFormat::ASCII}. + * + * Components: any number. + */ + const MODEL = 0x0110; + + /** + * Strip Offsets + * + * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}. + * + * Components: any number. + */ + const STRIP_OFFSETS = 0x0111; + + /** + * Orientation of image. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const ORIENTATION = 0x0112; + + /** + * Number of components. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const SAMPLES_PER_PIXEL = 0x0115; + + /** + * Rows per Strip + * + * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}. + * + * Components: 1. + */ + const ROWS_PER_STRIP = 0x0116; + + /** + * Strip Byte Count + * + * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}. + * + * Components: any number. + */ + const STRIP_BYTE_COUNTS = 0x0117; + + /** + * Image resolution in width direction. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const X_RESOLUTION = 0x011A; + + /** + * Image resolution in height direction. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const Y_RESOLUTION = 0x011B; + + /** + * Image data arrangement. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const PLANAR_CONFIGURATION = 0x011C; + + /** + * Unit of X and Y resolution. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const RESOLUTION_UNIT = 0x0128; + + /** + * Transfer function. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 3. + */ + const TRANSFER_FUNCTION = 0x012D; + + /** + * Software used. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: any number. + */ + const SOFTWARE = 0x0131; + + /** + * File change date and time. + * + * Format: {@link PelFormat::ASCII}, modelled by the {@link + * PelEntryTime} class. + * + * Components: 20. + */ + const DATE_TIME = 0x0132; + + /** + * Person who created the image. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: any number. + */ + const ARTIST = 0x013B; + + /** + * White point chromaticity. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 2. + */ + const WHITE_POINT = 0x013E; + + /** + * Chromaticities of primaries. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 6. + */ + const PRIMARY_CHROMATICITIES = 0x013F; + + /** + * Transfer Range + * + * Format: Unknown. + * + * Components: Unknown. + */ + const TRANSFER_RANGE = 0x0156; + + /** + * JPEGProc + * + * Format: Unknown. + * + * Components: Unknown. + */ + const JPEG_PROC = 0x0200; + + /** + * Offset to JPEG SOI. + * + * Format: {@link PelFormat::LONG}. + * + * Components: 1. + */ + const JPEG_INTERCHANGE_FORMAT = 0x0201; + + /** + * Bytes of JPEG data. + * + * Format: {@link PelFormat::LONG}. + * + * Components: 1. + */ + const JPEG_INTERCHANGE_FORMAT_LENGTH = 0x0202; + + /** + * Color space transformation matrix coefficients. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 3. + */ + const YCBCR_COEFFICIENTS = 0x0211; + + /** + * Subsampling ratio of Y to C. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 2. + */ + const YCBCR_SUB_SAMPLING = 0x0212; + + /** + * Y and C positioning. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const YCBCR_POSITIONING = 0x0213; + + /** + * Pair of black and white reference values. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 6. + */ + const REFERENCE_BLACK_WHITE = 0x0214; + + /** + * Related Image File Format + * + * Format: Unknown. + * + * Components: Unknown. + */ + const RELATED_IMAGE_FILE_FORMAT = 0x1000; + + /** + * Related Image Width + * + * Format: Unknown, probably {@link PelFormat::SHORT}? + * + * Components: Unknown, probably 1. + */ + const RELATED_IMAGE_WIDTH = 0x1001; + + /** Related Image Length + * + * Format: Unknown, probably {@link PelFormat::SHORT}? + * + * Components: Unknown, probably 1. + */ + const RELATED_IMAGE_LENGTH = 0x1002; + + /** + * CFA Repeat Pattern Dim. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 2. + */ + const CFA_REPEAT_PATTERN_DIM = 0x828D; + + /** + * Battery level. + * + * Format: Unknown. + * + * Components: Unknown. + */ + const BATTERY_LEVEL = 0x828F; + + /** + * Copyright holder. + * + * Format: {@link PelFormat::ASCII}, modelled by the {@link + * PelEntryCopyright} class. + * + * Components: any number. + */ + const COPYRIGHT = 0x8298; + + /** + * Exposure Time + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const EXPOSURE_TIME = 0x829A; + + /** + * FNumber + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const FNUMBER = 0x829D; + + /** + * IPTC/NAA + * + * Format: {@link PelFormat::LONG}. + * + * Components: any number. + */ + const IPTC_NAA = 0x83BB; + + /** + * Exif IFD Pointer + * + * Format: {@link PelFormat::LONG}. + * + * Components: 1. + */ + const EXIF_IFD_POINTER = 0x8769; + + /** + * Inter Color Profile + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: any number. + */ + const INTER_COLOR_PROFILE = 0x8773; + + /** + * Exposure Program + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const EXPOSURE_PROGRAM = 0x8822; + + /** + * Spectral Sensitivity + * + * Format: {@link PelFormat::ASCII}. + * + * Components: any number. + */ + const SPECTRAL_SENSITIVITY = 0x8824; + + /** + * GPS Info IFD Pointer + * + * Format: {@link PelFormat::LONG}. + * + * Components: 1. + */ + const GPS_INFO_IFD_POINTER = 0x8825; + + /** + * ISO Speed Ratings + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 2. + */ + const ISO_SPEED_RATINGS = 0x8827; + + /** + * OECF + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: any number. + */ + const OECF = 0x8828; + + /** + * Exif version. + * + * Format: {@link PelFormat::UNDEFINED}, modelled by the {@link + * PelEntryVersion} class. + * + * Components: 4. + */ + const EXIF_VERSION = 0x9000; + + /** + * Date and time of original data generation. + * + * Format: {@link PelFormat::ASCII}, modelled by the {@link + * PelEntryTime} class. + * + * Components: 20. + */ + const DATE_TIME_ORIGINAL = 0x9003; + + /** + * Date and time of digital data generation. + * + * Format: {@link PelFormat::ASCII}, modelled by the {@link + * PelEntryTime} class. + * + * Components: 20. + */ + const DATE_TIME_DIGITIZED = 0x9004; + + /** + * Meaning of each component. + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: 4. + */ + const COMPONENTS_CONFIGURATION = 0x9101; + + /** + * Image compression mode. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const COMPRESSED_BITS_PER_PIXEL = 0x9102; + + /** + * Shutter speed + * + * Format: {@link PelFormat::SRATIONAL}. + * + * Components: 1. + */ + const SHUTTER_SPEED_VALUE = 0x9201; + + /** + * Aperture + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const APERTURE_VALUE = 0x9202; + + /** + * Brightness + * + * Format: {@link PelFormat::SRATIONAL}. + * + * Components: 1. + */ + const BRIGHTNESS_VALUE = 0x9203; + + /** + * Exposure Bias + * + * Format: {@link PelFormat::SRATIONAL}. + * + * Components: 1. + */ + const EXPOSURE_BIAS_VALUE = 0x9204; + + /** + * Max Aperture Value + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const MAX_APERTURE_VALUE = 0x9205; + + /** + * Subject Distance + * + * Format: {@link PelFormat::SRATIONAL}. + * + * Components: 1. + */ + const SUBJECT_DISTANCE = 0x9206; + + /** + * Metering Mode + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const METERING_MODE = 0x9207; + + /** + * Light Source + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const LIGHT_SOURCE = 0x9208; + + /** + * Flash + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const FLASH = 0x9209; + + /** + * Focal Length + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const FOCAL_LENGTH = 0x920A; + + /** + * Subject Area + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 4. + */ + const SUBJECT_AREA = 0x9214; + + /** + * Maker Note + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: any number. + */ + const MAKER_NOTE = 0x927C; + + /** + * User Comment + * + * Format: {@link PelFormat::UNDEFINED}, modelled by the {@link + * PelEntryUserComment} class. + * + * Components: any number. + */ + const USER_COMMENT = 0x9286; + + /** + * SubSec Time + * + * Format: {@link PelFormat::ASCII}. + * + * Components: any number. + */ + const SUB_SEC_TIME = 0x9290; + + /** + * SubSec Time Original + * + * Format: {@link PelFormat::ASCII}. + * + * Components: any number. + */ + const SUB_SEC_TIME_ORIGINAL = 0x9291; + + /** + * SubSec Time Digitized + * + * Format: {@link PelFormat::ASCII}. + * + * Components: any number. + */ + const SUB_SEC_TIME_DIGITIZED = 0x9292; + + /** + * Windows XP Title + * + * Format: {@link PelFormat::BYTE}, modelled by the + * {@link PelEntryWindowsString} class. + * + * Components: any number. + */ + const XP_TITLE = 0x9C9B; + + + /** + * Windows XP Comment + * + * Format: {@link PelFormat::BYTE}, modelled by the + * {@link PelEntryWindowsString} class. + * + * Components: any number. + */ + const XP_COMMENT = 0x9C9C; + + + /** + * Windows XP Author + * + * Format: {@link PelFormat::BYTE}, modelled by the + * {@link PelEntryWindowsString} class. + * + * Components: any number. + */ + const XP_AUTHOR = 0x9C9D; + + + /** + * Windows XP Keywords + * + * Format: {@link PelFormat::BYTE}, modelled by the + * {@link PelEntryWindowsString} class. + * + * Components: any number. + */ + const XP_KEYWORDS = 0x9C9E; + + + /** + * Windows XP Subject + * + * Format: {@link PelFormat::BYTE}, modelled by the + * {@link PelEntryWindowsString} class. + * + * Components: any number. + */ + const XP_SUBJECT = 0x9C9F; + + + /** + * Supported Flashpix version + * + * Format: {@link PelFormat::UNDEFINED}, modelled by the {@link + * PelEntryVersion} class. + * + * Components: 4. + */ + const FLASH_PIX_VERSION = 0xA000; + + /** + * Color space information. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const COLOR_SPACE = 0xA001; + + /** + * Valid image width. + * + * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}. + * + * Components: 1. + */ + const PIXEL_X_DIMENSION = 0xA002; + + /** + * Valid image height. + * + * Format: {@link PelFormat::SHORT} or {@link PelFormat::LONG}. + * + * Components: 1. + */ + const PIXEL_Y_DIMENSION = 0xA003; + + /** + * Related audio file. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: any number. + */ + const RELATED_SOUND_FILE = 0xA004; + + /** + * Interoperability IFD Pointer + * + * Format: {@link PelFormat::LONG}. + * + * Components: 1. + */ + const INTEROPERABILITY_IFD_POINTER = 0xA005; + + /** + * Flash energy. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const FLASH_ENERGY = 0xA20B; + + /** + * Spatial frequency response. + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: any number. + */ + const SPATIAL_FREQUENCY_RESPONSE = 0xA20C; + + /** + * Focal plane X resolution. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const FOCAL_PLANE_X_RESOLUTION = 0xA20E; + + /** + * Focal plane Y resolution. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const FOCAL_PLANE_Y_RESOLUTION = 0xA20F; + + /** + * Focal plane resolution unit. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const FOCAL_PLANE_RESOLUTION_UNIT = 0xA210; + + /** + * Subject location. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const SUBJECT_LOCATION = 0xA214; + + /** + * Exposure index. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const EXPOSURE_INDEX = 0xA215; + + /** + * Sensing method. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const SENSING_METHOD = 0xA217; + + /** + * File source. + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: 1. + */ + const FILE_SOURCE = 0xA300; + + /** + * Scene type. + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: 1. + */ + const SCENE_TYPE = 0xA301; + + /** + * CFA pattern. + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: any number. + */ + const CFA_PATTERN = 0xA302; + + /** + * Custom image processing. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const CUSTOM_RENDERED = 0xA401; + + /** + * Exposure mode. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const EXPOSURE_MODE = 0xA402; + + /** + * White balance. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const WHITE_BALANCE = 0xA403; + + /** + * Digital zoom ratio. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const DIGITAL_ZOOM_RATIO = 0xA404; + + /** + * Focal length in 35mm film. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const FOCAL_LENGTH_IN_35MM_FILM = 0xA405; + + /** + * Scene capture type. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const SCENE_CAPTURE_TYPE = 0xA406; + + /** + * Gain control. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const GAIN_CONTROL = 0xA407; + + /** + * Contrast. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const CONTRAST = 0xA408; + + /** + * Saturation. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const SATURATION = 0xA409; + + /** + * Sharpness. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const SHARPNESS = 0xA40A; + + /** + * Device settings description. + * + * This tag indicates information on the picture-taking conditions + * of a particular camera model. The tag is used only to indicate + * the picture-taking conditions in the reader. + */ + const DEVICE_SETTING_DESCRIPTION = 0xA40B; + + /** + * Subject distance range. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const SUBJECT_DISTANCE_RANGE = 0xA40C; + + /** + * Image unique ID. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 32. + */ + const IMAGE_UNIQUE_ID = 0xA420; + + /** + * Gamma. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const GAMMA = 0xA500; + + /** + * PrintIM + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: unknown. + */ + const PRINT_IM = 0xC4A5; + + /** + * GPS tag version. + * + * Format: {@link PelFormat::BYTE}. + * + * Components: 4. + */ + const GPS_VERSION_ID = 0x0000; + + /** + * North or South Latitude. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_LATITUDE_REF = 0x0001; + + /** + * Latitude. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 3. + */ + const GPS_LATITUDE = 0x0002; + + /** + * East or West Longitude. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_LONGITUDE_REF = 0x0003; + + /** + * Longitude. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 3. + */ + const GPS_LONGITUDE = 0x0004; + + /** + * Altitude reference. + * + * Format: {@link PelFormat::BYTE}. + * + * Components: 1. + */ + const GPS_ALTITUDE_REF = 0x0005; + + /** + * Altitude. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const GPS_ALTITUDE = 0x0006; + + /** + * GPS time (atomic clock). + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 3. + */ + const GPS_TIME_STAMP = 0x0007; + + /** + * GPS satellites used for measurement. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: Any. + */ + const GPS_SATELLITES = 0x0008; + + /** + * GPS receiver status. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_STATUS = 0x0009; + + /** + * GPS measurement mode. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_MEASURE_MODE = 0x000A; + + /** + * Measurement precision. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const GPS_DOP = 0x000B; + + /** + * Speed unit. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_SPEED_REF = 0x000C; + + /** + * Speed of GPS receiver. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const GPS_SPEED = 0x000D; + + /** + * Reference for direction of movement. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_TRACK_REF = 0x000E; + + /** + * Direction of movement. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const GPS_TRACK = 0x000F; + + /** + * Reference for direction of image. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_IMG_DIRECTION_REF = 0x0010; + + /** + * Direction of image. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const GPS_IMG_DIRECTION = 0x0011; + + /** + * Geodetic survey data used. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: Any. + */ + const GPS_MAP_DATUM = 0x0012; + + /** + * Reference for latitude of destination. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_DEST_LATITUDE_REF = 0x0013; + + /** + * Latitude of destination. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 3. + */ + const GPS_DEST_LATITUDE = 0x0014; + + /** + * Reference for longitude of destination. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_DEST_LONGITUDE_REF = 0x0015; + + /** + * Longitude of destination. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 3. + */ + const GPS_DEST_LONGITUDE = 0x0016; + + /** + * Reference for bearing of destination. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_DEST_BEARING_REF = 0x0017; + + /** + * Bearing of destination. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const GPS_DEST_BEARING = 0x0018; + + /** + * Reference for distance to destination. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 2. + */ + const GPS_DEST_DISTANCE_REF = 0x0019; + + /** + * Distance to destination. + * + * Format: {@link PelFormat::RATIONAL}. + * + * Components: 1. + */ + const GPS_DEST_DISTANCE = 0x001A; + + /** + * Name of GPS processing method. + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: Any. + */ + const GPS_PROCESSING_METHOD = 0x001B; + + /** + * Name of GPS area. + * + * Format: {@link PelFormat::UNDEFINED}. + * + * Components: Any. + */ + const GPS_AREA_INFORMATION = 0x001C; + + /** + * GPS date. + * + * Format: {@link PelFormat::ASCII}. + * + * Components: 11. + */ + const GPS_DATE_STAMP = 0x001D; + + /** + * GPS differential correction. + * + * Format: {@link PelFormat::SHORT}. + * + * Components: 1. + */ + const GPS_DIFFERENTIAL = 0x001E; + + + /** + * Returns a short name for an Exif tag. + * + * @param int the IFD type of the tag, one of {@link PelIfd::IFD0}, + * {@link PelIfd::IFD1}, {@link PelIfd::EXIF}, {@link PelIfd::GPS}, + * or {@link PelIfd::INTEROPERABILITY}. + * + * @param PelTag the tag. + * + * @return string the short name of the tag, e.g., 'ImageWidth' for + * the {@link IMAGE_WIDTH} tag. If the tag is not known, the string + * 'Unknown:0xTTTT' will be returned where 'TTTT' is the hexadecimal + * representation of the tag. + */ + static function getName($type, $tag) { + + switch ($type) { + case PelIfd::IFD0: + case PelIfd::IFD1: + case PelIfd::EXIF: + case PelIfd::INTEROPERABILITY: + + switch ($tag) { + case self::INTEROPERABILITY_INDEX: + return 'InteroperabilityIndex'; + case self::INTEROPERABILITY_VERSION: + return 'InteroperabilityVersion'; + case self::IMAGE_WIDTH: + return 'ImageWidth'; + case self::IMAGE_LENGTH: + return 'ImageLength'; + case self::BITS_PER_SAMPLE: + return 'BitsPerSample'; + case self::COMPRESSION: + return 'Compression'; + case self::PHOTOMETRIC_INTERPRETATION: + return 'PhotometricInterpretation'; + case self::FILL_ORDER: + return 'FillOrder'; + case self::DOCUMENT_NAME: + return 'DocumentName'; + case self::IMAGE_DESCRIPTION: + return 'ImageDescription'; + case self::MAKE: + return 'Make'; + case self::MODEL: + return 'Model'; + case self::STRIP_OFFSETS: + return 'StripOffsets'; + case self::ORIENTATION: + return 'Orientation'; + case self::SAMPLES_PER_PIXEL: + return 'SamplesPerPixel'; + case self::ROWS_PER_STRIP: + return 'RowsPerStrip'; + case self::STRIP_BYTE_COUNTS: + return 'StripByteCounts'; + case self::X_RESOLUTION: + return 'XResolution'; + case self::Y_RESOLUTION: + return 'YResolution'; + case self::PLANAR_CONFIGURATION: + return 'PlanarConfiguration'; + case self::RESOLUTION_UNIT: + return 'ResolutionUnit'; + case self::TRANSFER_FUNCTION: + return 'TransferFunction'; + case self::SOFTWARE: + return 'Software'; + case self::DATE_TIME: + return 'DateTime'; + case self::ARTIST: + return 'Artist'; + case self::WHITE_POINT: + return 'WhitePoint'; + case self::PRIMARY_CHROMATICITIES: + return 'PrimaryChromaticities'; + case self::TRANSFER_RANGE: + return 'TransferRange'; + case self::JPEG_PROC: + return 'JPEGProc'; + case self::JPEG_INTERCHANGE_FORMAT: + return 'JPEGInterchangeFormat'; + case self::JPEG_INTERCHANGE_FORMAT_LENGTH: + return 'JPEGInterchangeFormatLength'; + case self::YCBCR_COEFFICIENTS: + return 'YCbCrCoefficients'; + case self::YCBCR_SUB_SAMPLING: + return 'YCbCrSubSampling'; + case self::YCBCR_POSITIONING: + return 'YCbCrPositioning'; + case self::REFERENCE_BLACK_WHITE: + return 'ReferenceBlackWhite'; + case self::RELATED_IMAGE_FILE_FORMAT: + return 'RelatedImageFileFormat'; + case self::RELATED_IMAGE_WIDTH: + return 'RelatedImageWidth'; + case self::RELATED_IMAGE_LENGTH: + return 'RelatedImageLength'; + case self::CFA_REPEAT_PATTERN_DIM: + return 'CFARepeatPatternDim'; + case self::CFA_PATTERN: + return 'CFAPattern'; + case self::BATTERY_LEVEL: + return 'BatteryLevel'; + case self::COPYRIGHT: + return 'Copyright'; + case self::EXPOSURE_TIME: + return 'ExposureTime'; + case self::FNUMBER: + return 'FNumber'; + case self::IPTC_NAA: + return 'IPTC/NAA'; + case self::EXIF_IFD_POINTER: + return 'ExifIFDPointer'; + case self::INTER_COLOR_PROFILE: + return 'InterColorProfile'; + case self::EXPOSURE_PROGRAM: + return 'ExposureProgram'; + case self::SPECTRAL_SENSITIVITY: + return 'SpectralSensitivity'; + case self::GPS_INFO_IFD_POINTER: + return 'GPSInfoIFDPointer'; + case self::ISO_SPEED_RATINGS: + return 'ISOSpeedRatings'; + case self::OECF: + return 'OECF'; + case self::EXIF_VERSION: + return 'ExifVersion'; + case self::DATE_TIME_ORIGINAL: + return 'DateTimeOriginal'; + case self::DATE_TIME_DIGITIZED: + return 'DateTimeDigitized'; + case self::COMPONENTS_CONFIGURATION: + return 'ComponentsConfiguration'; + case self::COMPRESSED_BITS_PER_PIXEL: + return 'CompressedBitsPerPixel'; + case self::SHUTTER_SPEED_VALUE: + return 'ShutterSpeedValue'; + case self::APERTURE_VALUE: + return 'ApertureValue'; + case self::BRIGHTNESS_VALUE: + return 'BrightnessValue'; + case self::EXPOSURE_BIAS_VALUE: + return 'ExposureBiasValue'; + case self::MAX_APERTURE_VALUE: + return 'MaxApertureValue'; + case self::SUBJECT_DISTANCE: + return 'SubjectDistance'; + case self::METERING_MODE: + return 'MeteringMode'; + case self::LIGHT_SOURCE: + return 'LightSource'; + case self::FLASH: + return 'Flash'; + case self::FOCAL_LENGTH: + return 'FocalLength'; + case self::MAKER_NOTE: + return 'MakerNote'; + case self::USER_COMMENT: + return 'UserComment'; + case self::SUB_SEC_TIME: + return 'SubSecTime'; + case self::SUB_SEC_TIME_ORIGINAL: + return 'SubSecTimeOriginal'; + case self::SUB_SEC_TIME_DIGITIZED: + return 'SubSecTimeDigitized'; + case self::XP_TITLE: + return 'WindowsXPTitle'; + case self::XP_COMMENT: + return 'WindowsXPComment'; + case self::XP_AUTHOR: + return 'WindowsXPAuthor'; + case self::XP_KEYWORDS: + return 'WindowsXPKeywords'; + case self::XP_SUBJECT: + return 'WindowsXPSubject'; + case self::FLASH_PIX_VERSION: + return 'FlashPixVersion'; + case self::COLOR_SPACE: + return 'ColorSpace'; + case self::PIXEL_X_DIMENSION: + return 'PixelXDimension'; + case self::PIXEL_Y_DIMENSION: + return 'PixelYDimension'; + case self::RELATED_SOUND_FILE: + return 'RelatedSoundFile'; + case self::INTEROPERABILITY_IFD_POINTER: + return 'InteroperabilityIFDPointer'; + case self::FLASH_ENERGY: + return 'FlashEnergy'; + case self::SPATIAL_FREQUENCY_RESPONSE: + return 'SpatialFrequencyResponse'; + case self::FOCAL_PLANE_X_RESOLUTION: + return 'FocalPlaneXResolution'; + case self::FOCAL_PLANE_Y_RESOLUTION: + return 'FocalPlaneYResolution'; + case self::FOCAL_PLANE_RESOLUTION_UNIT: + return 'FocalPlaneResolutionUnit'; + case self::SUBJECT_LOCATION: + return 'SubjectLocation'; + case self::EXPOSURE_INDEX: + return 'ExposureIndex'; + case self::SENSING_METHOD: + return 'SensingMethod'; + case self::FILE_SOURCE: + return 'FileSource'; + case self::SCENE_TYPE: + return 'SceneType'; + case self::SUBJECT_AREA: + return 'SubjectArea'; + case self::CUSTOM_RENDERED: + return 'CustomRendered'; + case self::EXPOSURE_MODE: + return 'ExposureMode'; + case self::WHITE_BALANCE: + return 'WhiteBalance'; + case self::DIGITAL_ZOOM_RATIO: + return 'DigitalZoomRatio'; + case self::FOCAL_LENGTH_IN_35MM_FILM: + return 'FocalLengthIn35mmFilm'; + case self::SCENE_CAPTURE_TYPE: + return 'SceneCaptureType'; + case self::GAIN_CONTROL: + return 'GainControl'; + case self::CONTRAST: + return 'Contrast'; + case self::SATURATION: + return 'Saturation'; + case self::SHARPNESS: + return 'Sharpness'; + case self::DEVICE_SETTING_DESCRIPTION: + return 'DeviceSettingDescription'; + case self::SUBJECT_DISTANCE_RANGE: + return 'SubjectDistanceRange'; + case self::IMAGE_UNIQUE_ID: + return 'ImageUniqueID'; + case self::GAMMA: + return 'Gamma'; + case self::PRINT_IM: + return 'PrintIM'; + } + + case PelIfd::GPS: + switch ($tag) { + case self::GPS_VERSION_ID: + return 'GPSVersionID'; + case self::GPS_LATITUDE_REF: + return 'GPSLatitudeRef'; + case self::GPS_LATITUDE: + return 'GPSLatitude'; + case self::GPS_LONGITUDE_REF: + return 'GPSLongitudeRef'; + case self::GPS_LONGITUDE: + return 'GPSLongitude'; + case self::GPS_ALTITUDE_REF: + return 'GPSAltitudeRef'; + case self::GPS_ALTITUDE: + return 'GPSAltitude'; + case self::GPS_TIME_STAMP: + return 'GPSTimeStamp'; + case self::GPS_SATELLITES: + return 'GPSSatellites'; + case self::GPS_STATUS: + return 'GPSStatus'; + case self::GPS_MEASURE_MODE: + return 'GPSMeasureMode'; + case self::GPS_DOP: + return 'GPSDOP'; + case self::GPS_SPEED_REF: + return 'GPSSpeedRef'; + case self::GPS_SPEED: + return 'GPSSpeed'; + case self::GPS_TRACK_REF: + return 'GPSTrackRef'; + case self::GPS_TRACK: + return 'GPSTrack'; + case self::GPS_IMG_DIRECTION_REF: + return 'GPSImgDirectionRef'; + case self::GPS_IMG_DIRECTION: + return 'GPSImgDirection'; + case self::GPS_MAP_DATUM: + return 'GPSMapDatum'; + case self::GPS_DEST_LATITUDE_REF: + return 'GPSDestLatitudeRef'; + case self::GPS_DEST_LATITUDE: + return 'GPSDestLatitude'; + case self::GPS_DEST_LONGITUDE_REF: + return 'GPSDestLongitudeRef'; + case self::GPS_DEST_LONGITUDE: + return 'GPSDestLongitude'; + case self::GPS_DEST_BEARING_REF: + return 'GPSDestBearingRef'; + case self::GPS_DEST_BEARING: + return 'GPSDestBearing'; + case self::GPS_DEST_DISTANCE_REF: + return 'GPSDestDistanceRef'; + case self::GPS_DEST_DISTANCE: + return 'GPSDestDistance'; + case self::GPS_PROCESSING_METHOD: + return 'GPSProcessingMethod'; + case self::GPS_AREA_INFORMATION: + return 'GPSAreaInformation'; + case self::GPS_DATE_STAMP: + return 'GPSDateStamp'; + case self::GPS_DIFFERENTIAL: + return 'GPSDifferential'; + } + + default: + return Pel::fmt('Unknown: 0x%04X', $tag); + } + } + + + /** + * Returns a title for an Exif tag. + * + * @param int the IFD type of the tag, one of {@link PelIfd::IFD0}, + * {@link PelIfd::IFD1}, {@link PelIfd::EXIF}, {@link PelIfd::GPS}, + * or {@link PelIfd::INTEROPERABILITY}. + * + * @param PelTag the tag. + * + * @return string the title of the tag, e.g., 'Image Width' for the + * {@link IMAGE_WIDTH} tag. If the tag isn't known, the string + * 'Unknown Tag: 0xTT' will be returned where 'TT' is the + * hexadecimal representation of the tag. + */ + function getTitle($type, $tag) { + + switch ($type) { + case PelIfd::IFD0: + case PelIfd::IFD1: + case PelIfd::EXIF: + case PelIfd::INTEROPERABILITY: + + switch ($tag) { + case self::INTEROPERABILITY_INDEX: + return Pel::tra('Interoperability Index'); + case self::INTEROPERABILITY_VERSION: + return Pel::tra('Interoperability Version'); + case self::IMAGE_WIDTH: + return Pel::tra('Image Width'); + case self::IMAGE_LENGTH: + return Pel::tra('Image Length'); + case self::BITS_PER_SAMPLE: + return Pel::tra('Bits per Sample'); + case self::COMPRESSION: + return Pel::tra('Compression'); + case self::PHOTOMETRIC_INTERPRETATION: + return Pel::tra('Photometric Interpretation'); + case self::FILL_ORDER: + return Pel::tra('Fill Order'); + case self::DOCUMENT_NAME: + return Pel::tra('Document Name'); + case self::IMAGE_DESCRIPTION: + return Pel::tra('Image Description'); + case self::MAKE: + return Pel::tra('Manufacturer'); + case self::MODEL: + return Pel::tra('Model'); + case self::STRIP_OFFSETS: + return Pel::tra('Strip Offsets'); + case self::ORIENTATION: + return Pel::tra('Orientation'); + case self::SAMPLES_PER_PIXEL: + return Pel::tra('Samples per Pixel'); + case self::ROWS_PER_STRIP: + return Pel::tra('Rows per Strip'); + case self::STRIP_BYTE_COUNTS: + return Pel::tra('Strip Byte Count'); + case self::X_RESOLUTION: + return Pel::tra('x-Resolution'); + case self::Y_RESOLUTION: + return Pel::tra('y-Resolution'); + case self::PLANAR_CONFIGURATION: + return Pel::tra('Planar Configuration'); + case self::RESOLUTION_UNIT: + return Pel::tra('Resolution Unit'); + case self::TRANSFER_FUNCTION: + return Pel::tra('Transfer Function'); + case self::SOFTWARE: + return Pel::tra('Software'); + case self::DATE_TIME: + return Pel::tra('Date and Time'); + case self::ARTIST: + return Pel::tra('Artist'); + case self::WHITE_POINT: + return Pel::tra('White Point'); + case self::PRIMARY_CHROMATICITIES: + return Pel::tra('Primary Chromaticities'); + case self::TRANSFER_RANGE: + return Pel::tra('Transfer Range'); + case self::JPEG_PROC: + return Pel::tra('JPEG Process'); + case self::JPEG_INTERCHANGE_FORMAT: + return Pel::tra('JPEG Interchange Format'); + case self::JPEG_INTERCHANGE_FORMAT_LENGTH: + return Pel::tra('JPEG Interchange Format Length'); + case self::YCBCR_COEFFICIENTS: + return Pel::tra('YCbCr Coefficients'); + case self::YCBCR_SUB_SAMPLING: + return Pel::tra('YCbCr Sub-Sampling'); + case self::YCBCR_POSITIONING: + return Pel::tra('YCbCr Positioning'); + case self::REFERENCE_BLACK_WHITE: + return Pel::tra('Reference Black/White'); + case self::RELATED_IMAGE_FILE_FORMAT: + return Pel::tra('Related Image File Format'); + case self::RELATED_IMAGE_WIDTH: + return Pel::tra('Related Image Width'); + case self::RELATED_IMAGE_LENGTH: + return Pel::tra('Related Image Length'); + case self::CFA_REPEAT_PATTERN_DIM: + return Pel::tra('CFA Repeat Pattern Dim'); + case self::CFA_PATTERN: + return Pel::tra('CFA Pattern'); + case self::BATTERY_LEVEL: + return Pel::tra('Battery Level'); + case self::COPYRIGHT: + return Pel::tra('Copyright'); + case self::EXPOSURE_TIME: + return Pel::tra('Exposure Time'); + case self::FNUMBER: + return Pel::tra('FNumber'); + case self::IPTC_NAA: + return Pel::tra('IPTC/NAA'); + case self::EXIF_IFD_POINTER: + return Pel::tra('Exif IFD Pointer'); + case self::INTER_COLOR_PROFILE: + return Pel::tra('Inter Color Profile'); + case self::EXPOSURE_PROGRAM: + return Pel::tra('Exposure Program'); + case self::SPECTRAL_SENSITIVITY: + return Pel::tra('Spectral Sensitivity'); + case self::GPS_INFO_IFD_POINTER: + return Pel::tra('GPS Info IFD Pointer'); + case self::ISO_SPEED_RATINGS: + return Pel::tra('ISO Speed Ratings'); + case self::OECF: + return Pel::tra('OECF'); + case self::EXIF_VERSION: + return Pel::tra('Exif Version'); + case self::DATE_TIME_ORIGINAL: + return Pel::tra('Date and Time (original)'); + case self::DATE_TIME_DIGITIZED: + return Pel::tra('Date and Time (digitized)'); + case self::COMPONENTS_CONFIGURATION: + return Pel::tra('Components Configuration'); + case self::COMPRESSED_BITS_PER_PIXEL: + return Pel::tra('Compressed Bits per Pixel'); + case self::SHUTTER_SPEED_VALUE: + return Pel::tra('Shutter speed'); + case self::APERTURE_VALUE: + return Pel::tra('Aperture'); + case self::BRIGHTNESS_VALUE: + return Pel::tra('Brightness'); + case self::EXPOSURE_BIAS_VALUE: + return Pel::tra('Exposure Bias'); + case self::MAX_APERTURE_VALUE: + return Pel::tra('Max Aperture Value'); + case self::SUBJECT_DISTANCE: + return Pel::tra('Subject Distance'); + case self::METERING_MODE: + return Pel::tra('Metering Mode'); + case self::LIGHT_SOURCE: + return Pel::tra('Light Source'); + case self::FLASH: + return Pel::tra('Flash'); + case self::FOCAL_LENGTH: + return Pel::tra('Focal Length'); + case self::MAKER_NOTE: + return Pel::tra('Maker Note'); + case self::USER_COMMENT: + return Pel::tra('User Comment'); + case self::SUB_SEC_TIME: + return Pel::tra('SubSec Time'); + case self::SUB_SEC_TIME_ORIGINAL: + return Pel::tra('SubSec Time Original'); + case self::SUB_SEC_TIME_DIGITIZED: + return Pel::tra('SubSec Time Digitized'); + case self::XP_TITLE: + return 'Windows XP Title'; + case self::XP_COMMENT: + return 'Windows XP Comment'; + case self::XP_AUTHOR: + return 'Windows XP Author'; + case self::XP_KEYWORDS: + return 'Windows XP Keywords'; + case self::XP_SUBJECT: + return 'Windows XP Subject'; + case self::FLASH_PIX_VERSION: + return Pel::tra('FlashPix Version'); + case self::COLOR_SPACE: + return Pel::tra('Color Space'); + case self::PIXEL_X_DIMENSION: + return Pel::tra('Pixel x-Dimension'); + case self::PIXEL_Y_DIMENSION: + return Pel::tra('Pixel y-Dimension'); + case self::RELATED_SOUND_FILE: + return Pel::tra('Related Sound File'); + case self::INTEROPERABILITY_IFD_POINTER: + return Pel::tra('Interoperability IFD Pointer'); + case self::FLASH_ENERGY: + return Pel::tra('Flash Energy'); + case self::SPATIAL_FREQUENCY_RESPONSE: + return Pel::tra('Spatial Frequency Response'); + case self::FOCAL_PLANE_X_RESOLUTION: + return Pel::tra('Focal Plane x-Resolution'); + case self::FOCAL_PLANE_Y_RESOLUTION: + return Pel::tra('Focal Plane y-Resolution'); + case self::FOCAL_PLANE_RESOLUTION_UNIT: + return Pel::tra('Focal Plane Resolution Unit'); + case self::SUBJECT_LOCATION: + return Pel::tra('Subject Location'); + case self::EXPOSURE_INDEX: + return Pel::tra('Exposure index'); + case self::SENSING_METHOD: + return Pel::tra('Sensing Method'); + case self::FILE_SOURCE: + return Pel::tra('File Source'); + case self::SCENE_TYPE: + return Pel::tra('Scene Type'); + case self::SUBJECT_AREA: + return Pel::tra('Subject Area'); + case self::CUSTOM_RENDERED: + return Pel::tra('Custom Rendered'); + case self::EXPOSURE_MODE: + return Pel::tra('Exposure Mode'); + case self::WHITE_BALANCE: + return Pel::tra('White Balance'); + case self::DIGITAL_ZOOM_RATIO: + return Pel::tra('Digital Zoom Ratio'); + case self::FOCAL_LENGTH_IN_35MM_FILM: + return Pel::tra('Focal Length In 35mm Film'); + case self::SCENE_CAPTURE_TYPE: + return Pel::tra('Scene Capture Type'); + case self::GAIN_CONTROL: + return Pel::tra('Gain Control'); + case self::CONTRAST: + return Pel::tra('Contrast'); + case self::SATURATION: + return Pel::tra('Saturation'); + case self::SHARPNESS: + return Pel::tra('Sharpness'); + case self::DEVICE_SETTING_DESCRIPTION: + return Pel::tra('Device Setting Description'); + case self::SUBJECT_DISTANCE_RANGE: + return Pel::tra('Subject Distance Range'); + case self::IMAGE_UNIQUE_ID: + return Pel::tra('Image Unique ID'); + case self::GAMMA: + return Pel::tra('Gamma'); + case self::PRINT_IM: + return Pel::tra('Print IM'); + } + + case PelIfd::GPS: + switch ($tag) { + case self::GPS_VERSION_ID: + return 'GPSVersionID'; + case self::GPS_LATITUDE_REF: + return 'GPSLatitudeRef'; + case self::GPS_LATITUDE: + return 'GPSLatitude'; + case self::GPS_LONGITUDE_REF: + return 'GPSLongitudeRef'; + case self::GPS_LONGITUDE: + return 'GPSLongitude'; + case self::GPS_ALTITUDE_REF: + return 'GPSAltitudeRef'; + case self::GPS_ALTITUDE: + return 'GPSAltitude'; + case self::GPS_TIME_STAMP: + return 'GPSTimeStamp'; + case self::GPS_SATELLITES: + return 'GPSSatellites'; + case self::GPS_STATUS: + return 'GPSStatus'; + case self::GPS_MEASURE_MODE: + return 'GPSMeasureMode'; + case self::GPS_DOP: + return 'GPSDOP'; + case self::GPS_SPEED_REF: + return 'GPSSpeedRef'; + case self::GPS_SPEED: + return 'GPSSpeed'; + case self::GPS_TRACK_REF: + return 'GPSTrackRef'; + case self::GPS_TRACK: + return 'GPSTrack'; + case self::GPS_IMG_DIRECTION_REF: + return 'GPSImgDirectionRef'; + case self::GPS_IMG_DIRECTION: + return 'GPSImgDirection'; + case self::GPS_MAP_DATUM: + return 'GPSMapDatum'; + case self::GPS_DEST_LATITUDE_REF: + return 'GPSDestLatitudeRef'; + case self::GPS_DEST_LATITUDE: + return 'GPSDestLatitude'; + case self::GPS_DEST_LONGITUDE_REF: + return 'GPSDestLongitudeRef'; + case self::GPS_DEST_LONGITUDE: + return 'GPSDestLongitude'; + case self::GPS_DEST_BEARING_REF: + return 'GPSDestBearingRef'; + case self::GPS_DEST_BEARING: + return 'GPSDestBearing'; + case self::GPS_DEST_DISTANCE_REF: + return 'GPSDestDistanceRef'; + case self::GPS_DEST_DISTANCE: + return 'GPSDestDistance'; + case self::GPS_PROCESSING_METHOD: + return 'GPSProcessingMethod'; + case self::GPS_AREA_INFORMATION: + return 'GPSAreaInformation'; + case self::GPS_DATE_STAMP: + return 'GPSDateStamp'; + case self::GPS_DIFFERENTIAL: + return 'GPSDifferential'; + } + + default: + return Pel::fmt('Unknown Tag: 0x%04X', $tag); + } + } + +} + diff --git a/modules/autorotate/lib/pel/PelTiff.php b/modules/autorotate/lib/pel/PelTiff.php new file mode 100644 index 0000000..6f4af75 --- /dev/null +++ b/modules/autorotate/lib/pel/PelTiff.php @@ -0,0 +1,297 @@ +<?php + +/** + * PEL: PHP Exif Library. A library with support for reading and + * writing all Exif headers in JPEG and TIFF images using PHP. + * + * Copyright (C) 2004, 2005, 2006 Martin Geisler. + * + * 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 in the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* $Id$ */ + + +/** + * Classes for dealing with TIFF data. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @version $Revision$ + * @date $Date$ + * @license http://www.gnu.org/licenses/gpl.html GNU General Public + * License (GPL) + * @package PEL + */ + +/**#@+ Required class definitions. */ +require_once('PelDataWindow.php'); +require_once('PelIfd.php'); +require_once('Pel.php'); +/**#@-*/ + + +/** + * Class for handling TIFF data. + * + * Exif data is actually an extension of the TIFF file format. TIFF + * images consist of a number of {@link PelIfd Image File Directories} + * (IFDs), each containing a number of {@link PelEntry entries}. The + * IFDs are linked to each other --- one can get hold of the first one + * with the {@link getIfd()} method. + * + * To parse a TIFF image for Exif data one would do: + * + * <code> + * $tiff = new PelTiff($data); + * $ifd0 = $tiff->getIfd(); + * $exif = $ifd0->getSubIfd(PelIfd::EXIF); + * $ifd1 = $ifd0->getNextIfd(); + * </code> + * + * Should one have some image data of an unknown type, then the {@link + * PelTiff::isValid()} function is handy: it will quickly test if the + * data could be valid TIFF data. The {@link PelJpeg::isValid()} + * function does the same for JPEG images. + * + * @author Martin Geisler <mgeisler@users.sourceforge.net> + * @package PEL + */ +class PelTiff { + + /** + * TIFF header. + * + * This must follow after the two bytes indicating the byte order. + */ + const TIFF_HEADER = 0x002A; + + /** + * The first Image File Directory, if any. + * + * If set, then the type of the IFD must be {@link PelIfd::IFD0}. + * + * @var PelIfd + */ + private $ifd = null; + + + /** + * Construct a new object for holding TIFF data. + * + * The new object will be empty (with no {@link PelIfd}) unless an + * argument is given from which it can initialize itself. This can + * either be the filename of a TIFF image or a {@link PelDataWindow} + * object. + * + * Use {@link setIfd()} to explicitly set the IFD. + */ + function __construct($data = false) { + if ($data === false) + return; + + if (is_string($data)) { + Pel::debug('Initializing PelTiff object from %s', $data); + $this->loadFile($data); + } elseif ($data instanceof PelDataWindow) { + Pel::debug('Initializing PelTiff object from PelDataWindow.'); + $this->load($data); + } else { + throw new PelInvalidArgumentException('Bad type for $data: %s', + gettype($data)); + } + } + + + /** + * Load TIFF data. + * + * The data given will be parsed and an internal tree representation + * will be built. If the data cannot be parsed correctly, a {@link + * PelInvalidDataException} is thrown, explaining the problem. + * + * @param PelDataWindow the data from which the object will be + * constructed. This should be valid TIFF data, coming either + * directly from a TIFF image or from the Exif data in a JPEG image. + */ + function load(PelDataWindow $d) { + Pel::debug('Parsing %d bytes of TIFF data...', $d->getSize()); + + /* There must be at least 8 bytes available: 2 bytes for the byte + * order, 2 bytes for the TIFF header, and 4 bytes for the offset + * to the first IFD. */ + if ($d->getSize() < 8) + throw new PelInvalidDataException('Expected at least 8 bytes of TIFF ' . + 'data, found just %d bytes.', + $d->getSize()); + + /* Byte order */ + if ($d->strcmp(0, 'II')) { + Pel::debug('Found Intel byte order'); + $d->setByteOrder(PelConvert::LITTLE_ENDIAN); + } elseif ($d->strcmp(0, 'MM')) { + Pel::debug('Found Motorola byte order'); + $d->setByteOrder(PelConvert::BIG_ENDIAN); + } else { + throw new PelInvalidDataException('Unknown byte order found in TIFF ' . + 'data: 0x%2X%2X', + $d->getByte(0), $d->getByte(1)); + } + + /* Verify the TIFF header */ + if ($d->getShort(2) != self::TIFF_HEADER) + throw new PelInvalidDataException('Missing TIFF magic value.'); + + /* IFD 0 offset */ + $offset = $d->getLong(4); + Pel::debug('First IFD at offset %d.', $offset); + + if ($offset > 0) { + /* Parse the first IFD, this will automatically parse the + * following IFDs and any sub IFDs. */ + $this->ifd = new PelIfd(PelIfd::IFD0); + $this->ifd->load($d, $offset); + } + } + + + /** + * Load data from a file into a TIFF object. + * + * @param string the filename. This must be a readable file. + */ + function loadFile($filename) { + $this->load(new PelDataWindow(file_get_contents($filename))); + } + + + /** + * Set the first IFD. + * + * @param PelIfd the new first IFD, which must be of type {@link + * PelIfd::IFD0}. + */ + function setIfd(PelIfd $ifd) { + if ($ifd->getType() != PelIfd::IFD0) + throw new PelInvalidDataException('Invalid type of IFD: %d, expected %d.', + $ifd->getType(), PelIfd::IFD0); + + $this->ifd = $ifd; + } + + + /** + * Return the first IFD. + * + * @return PelIfd the first IFD contained in the TIFF data, if any. + * If there is no IFD null will be returned. + */ + function getIfd() { + return $this->ifd; + } + + + /** + * Turn this object into bytes. + * + * TIFF images can have {@link PelConvert::LITTLE_ENDIAN + * little-endian} or {@link PelConvert::BIG_ENDIAN big-endian} byte + * order, and so this method takes an argument specifying that. + * + * @param PelByteOrder the desired byte order of the TIFF data. + * This should be one of {@link PelConvert::LITTLE_ENDIAN} or {@link + * PelConvert::BIG_ENDIAN}. + * + * @return string the bytes representing this object. + */ + function getBytes($order = PelConvert::LITTLE_ENDIAN) { + if ($order == PelConvert::LITTLE_ENDIAN) + $bytes = 'II'; + else + $bytes = 'MM'; + + /* TIFF magic number --- fixed value. */ + $bytes .= PelConvert::shortToBytes(self::TIFF_HEADER, $order); + + if ($this->ifd != null) { + /* IFD 0 offset. We will always start IDF 0 at an offset of 8 + * bytes (2 bytes for byte order, another 2 bytes for the TIFF + * header, and 4 bytes for the IFD 0 offset make 8 bytes + * together). + */ + $bytes .= PelConvert::longToBytes(8, $order); + + /* The argument specifies the offset of this IFD. The IFD will + * use this to calculate offsets from the entries to their data, + * all those offsets are absolute offsets counted from the + * beginning of the data. */ + $bytes .= $this->ifd->getBytes(8, $order); + } else { + $bytes .= PelConvert::longToBytes(0, $order); + } + + return $bytes; + } + + + /** + * Return a string representation of this object. + * + * @return string a string describing this object. This is mostly useful + * for debugging. + */ + function __toString() { + $str = Pel::fmt("Dumping TIFF data...\n"); + if ($this->ifd != null) + $str .= $this->ifd->__toString(); + + return $str; + } + + + /** + * Check if data is valid TIFF data. + * + * This will read just enough data from the data window to determine + * if the data could be a valid TIFF data. This means that the + * check is more like a heuristic than a rigorous check. + * + * @param PelDataWindow the bytes that will be examined. + * + * @return boolean true if the data looks like valid TIFF data, + * false otherwise. + * + * @see PelJpeg::isValid() + */ + static function isValid(PelDataWindow $d) { + /* First check that we have enough data. */ + if ($d->getSize() < 8) + return false; + + /* Byte order */ + if ($d->strcmp(0, 'II')) { + $d->setByteOrder(PelConvert::LITTLE_ENDIAN); + } elseif ($d->strcmp(0, 'MM')) { + Pel::debug('Found Motorola byte order'); + $d->setByteOrder(PelConvert::BIG_ENDIAN); + } else { + return false; + } + + /* Verify the TIFF header */ + return $d->getShort(2) == self::TIFF_HEADER; + } + +}
\ No newline at end of file diff --git a/modules/autorotate/module.info b/modules/autorotate/module.info new file mode 100644 index 0000000..e087ec4 --- /dev/null +++ b/modules/autorotate/module.info @@ -0,0 +1,7 @@ +name = "Autorotate" +description = "Rotate an image automatically on upload based on EXIF data" +version = 3 +author_name = "" +author_url = "" +info_url = "http://codex.galleryproject.org/Gallery3:Modules:autorotate" +discuss_url = "http://galleryproject.org/node/97270" diff --git a/modules/batchtag/controllers/batchtag.php b/modules/batchtag/controllers/batchtag.php new file mode 100644 index 0000000..c5437b6 --- /dev/null +++ b/modules/batchtag/controllers/batchtag.php @@ -0,0 +1,117 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 BatchTag_Controller extends Controller { + public function tagitems() { + // Tag all non-album items in the current album with the specified tags. + + // Prevent Cross Site Request Forgery + access::verify_csrf(); + + $input = Input::instance(); + url::redirect(url::abs_site("batchtag/tagitems2?name={$input->post('name')}&item_id={$input->post('item_id')}&tag_subitems={$input->post('tag_subitems')}&csrf={$input->post('csrf')}")); + + } + + public function tagitems2() { + // Tag all non-album items in the current album with the specified tags. + + // Prevent Cross Site Request Forgery + access::verify_csrf(); + + $input = Input::instance(); + + // Variables + if (($input->get("batchtag_max") == false) || ($input->get("batchtag_max") == "0")) { + $batchtag_max = "50"; + } else { + $batchtag_max = $input->get("batchtag_max"); + } + if ($input->get("batchtag_items_processed") == false) { + $batchtag_items_processed = "0"; + } else { + $batchtag_items_processed = $input->get("batchtag_items_processed"); + } + + // Figure out if the contents of sub-albums should also be tagged + $str_tag_subitems = $input->get("tag_subitems"); + + $children = ""; + if ($str_tag_subitems == false) { + // Generate an array of all non-album items in the current album. + $children = ORM::factory("item") + ->where("parent_id", "=", $input->get("item_id")) + ->where("type", "!=", "album") + ->find_all(); + } else { + // Generate an array of all non-album items in the current album + // and any sub albums. + $item = ORM::factory("item", $input->get("item_id")); + $children = $item->descendants(); + } + + // Loop through each item in the album and make sure the user has + // access to view and edit it. + $children_count = "0"; + $tag_count = "0"; + + //echo Kohana::debug($children); + + echo '<style>.continue { margin: 5em auto; text-align: center; }</style>'; + + foreach ($children as $child) { + + if ($tag_count < $batchtag_max) { + + if ($children_count >= $batchtag_items_processed) { + if (access::can("view", $child) && access::can("edit", $child) && !$child->is_album()) { + + // Assuming the user can view/edit the current item, loop + // through each tag that was submitted and apply it to + // the current item. + foreach (explode(",", $input->get("name")) as $tag_name) { + $tag_name = trim($tag_name); + if ($tag_name) { + tag::add($child, $tag_name); + } + // $tag_count should be inside the foreach loop as it is depending on the number of time tag:add is run + $tag_count++; + } + } + echo '<style>.c' . $children_count . ' { display:none; }</style>' . "\n"; + $children_count++; + $batchtag_max_new = $tag_count; + echo '<div class="continue c' . $children_count . '"><a href="' . url::abs_site("batchtag/tagitems2?name={$input->get('name')}&item_id={$input->get('item_id')}&tag_subitems={$input->get('tag_subitems')}&batchtag_items_processed=$children_count&batchtag_max=$batchtag_max_new&csrf={$input->get('csrf')}") . '">Continue</a></div>'; + } else { $children_count++; } + + } else { break; } + + } + + if ($tag_count < $batchtag_max) { + // Redirect back to the album. + $item = ORM::factory("item", $input->get("item_id")); + url::redirect(url::abs_site("{$item->type}s/{$item->id}")); + //echo url::abs_site("{$item->type}s/{$item->id}"); + } else { + url::redirect(url::abs_site("batchtag/tagitems2?name={$input->get('name')}&item_id={$input->get('item_id')}&tag_subitems={$input->get('tag_subitems')}&batchtag_items_processed=$children_count&batchtag_max=$batchtag_max&csrf={$input->get('csrf')}")); + //echo url::abs_site("batchtag/tagitems2?name={$input->get('name')}&item_id={$input->get('item_id')}&tag_subitems={$input->get('tag_subitems')}&batchtag_items_processed=$children_count&batchtag_max=$batchtag_max&csrf={$input->get('csrf')}"); + } + } +} diff --git a/modules/batchtag/helpers/batchtag_block.php b/modules/batchtag/helpers/batchtag_block.php new file mode 100644 index 0000000..897a881 --- /dev/null +++ b/modules/batchtag/helpers/batchtag_block.php @@ -0,0 +1,61 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 batchtag_block_Core { + static function get_site_list() { + return array("batch_tag" => t("Batch Tag")); + } + + static function get($block_id, $theme) { + $block = ""; + + // Only display on album pages that the user can edit. + $item = $theme->item(); + if (!$item || !$item->is_album() || !access::can("edit", $item)) { + return; + } + + switch ($block_id) { + case "batch_tag": + // Make a new sidebar block. + $block = new Block(); + $block->css_id = "g-batch-tag"; + $block->title = t("Batch Tag"); + $block->content = new View("batchtag_block.html"); + + // Make a new form to place in the sidebar block. + $form = new Forge("batchtag/tagitems", "", "post", + array("id" => "g-batch-tag-form")); + $label = t("Tag everything in this album:"); + $group = $form->group("add_tag")->label("Add Tag"); + $group->input("name")->label($label)->rules("required|length[1,64]"); + $group->checkbox("tag_subitems") + ->label(t("Include sub-albums?")) + ->value(true) + ->checked(false); + + $group->hidden("item_id")->value($item->id); + $group->submit("")->value(t("Add Tag")); + $block->content->batch_tag_form = $form; + + break; + } + return $block; + } +} diff --git a/modules/batchtag/helpers/batchtag_event.php b/modules/batchtag/helpers/batchtag_event.php new file mode 100644 index 0000000..4a0eebe --- /dev/null +++ b/modules/batchtag/helpers/batchtag_event.php @@ -0,0 +1,40 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 batchtag_event_Core { + static function pre_deactivate($data) { + if ($data->module == "tag") { + $data->messages["warn"][] = t("The BatchTag module requires the Tags module."); + } + } + + static function module_change($changes) { + // See if the Tags module is installed, + // tell the user to install it if it isn't. + if (!module::is_active("tag") || in_array("tag", $changes->deactivate)) { + site_status::warning( + t("The BatchTag module requires the Tags module. " . + "<a href=\"%url\">Activate the Tags module now</a>", + array("url" => url::site("admin/modules"))), + "batchtag_needs_tag"); + } else { + site_status::clear("batchtag_needs_tag"); + } + } +} diff --git a/modules/batchtag/helpers/batchtag_installer.php b/modules/batchtag/helpers/batchtag_installer.php new file mode 100644 index 0000000..a97bb10 --- /dev/null +++ b/modules/batchtag/helpers/batchtag_installer.php @@ -0,0 +1,41 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 batchtag_installer { + static function install() { + // Set the module's version number. + module::set_version("batchtag", 1); + } + + static function deactivate() { + site_status::clear("batchtag_needs_tag"); + } + + static function can_activate() { + $messages = array(); + if (!module::is_active("tag")) { + $messages["warn"][] = t("The BatchTag module requires the Tags module."); + } + return $messages; + } + + static function uninstall() { + module::delete("batchtag"); + } +} diff --git a/modules/batchtag/module.info b/modules/batchtag/module.info new file mode 100644 index 0000000..c1380ad --- /dev/null +++ b/modules/batchtag/module.info @@ -0,0 +1,7 @@ +name = "BatchTag" +description = "Automatically apply a tag to the entire contents of an album." +version = 1 +author_name = "rWatcher" +author_url = "http://codex.gallery2.org/User:RWatcher" +info_url = "http://codex.gallery2.org/Gallery3:Modules:batchtag" +discuss_url = "http://gallery.menalto.com/node/101076" diff --git a/modules/batchtag/views/batchtag_block.html.php b/modules/batchtag/views/batchtag_block.html.php new file mode 100644 index 0000000..c46603c --- /dev/null +++ b/modules/batchtag/views/batchtag_block.html.php @@ -0,0 +1,15 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<script type="text/javascript"> + $("#g-batch-tag-form").ready(function() { + var url = "<?= url::site("tags") ?>" + "/autocomplete"; + $("#g-batch-tag-form input:text").autocomplete( + url, { + max: 30, + multiple: true, + multipleSeparator: ',', + cacheLength: 1 + } + ); + }); +</script> +<?= $batch_tag_form ?> diff --git a/modules/developer/config/developer.php b/modules/developer/config/developer.php new file mode 100644 index 0000000..c9abb92 --- /dev/null +++ b/modules/developer/config/developer.php @@ -0,0 +1,54 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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. + */ + +/** + * Defines the available callback methods + */ +$config["methods"] = array( + "theme" => array("album_blocks" => t("Album block"), + "album_bottom" => t("Bottom of album content"), + "album_top" => t("Top of Album content"), + "admin_credits" => t("Administration page credits"), + "admin_footer" => t("Adminsitration page footer"), + "admin_header_top" => t("Top of administration page header"), + "admin_header_bottom" => t("Bottom of administration page header"), + "admin_page_bottom" => t("Bottom of administration page"), + "admin_page_top" => t("Top of administration page"), + "admin_head" => t("Adminstration page head"), + "body_attributes" => t("Body Attributes"), + "credits" => t("Album or photo page credits"), + "dynamic_bottom" => t("Bottom of dynamic page content"), + "dynamic_top" => t("Top of dynamic page content"), + "footer" => t("Album or photo page footer"), + "head" => t("Album or photo page head"), + "header_bottom" => t("Album or photo header bottom"), + "header_top" => t("Album or photo header top"), + "page_bottom" => t("Album or photo bottom"), + "page_top" => t("Album or photo top"), + "photo_blocks" => t("Photo block"), + "photo_bottom" => t("Bottom of photo content"), + "photo_top" => t("Top of photo content"), + "resize_bottom" => t("Bottom of the resize view"), + "resize_top" => t("Top of the resize view"), + "sidebar_bottom" => t("Bottom of sidebar"), + "sidebar_top" => t("Top of sidebar"), + "thumb_bottom" => t("Bottom of thumbnail"), + "thumb_info" => t("Thumbnail information"), + "thumb_top" => t("Top of thumbnail display"))); diff --git a/modules/developer/controllers/admin_developer.php b/modules/developer/controllers/admin_developer.php new file mode 100644 index 0000000..d723a36 --- /dev/null +++ b/modules/developer/controllers/admin_developer.php @@ -0,0 +1,306 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 Admin_Developer_Controller extends Admin_Controller { + static $event_list = array(); + + public function module() { + $view = new Admin_View("admin.html"); + $view->content = new View("admin_developer.html"); + $view->content->title = t("Generate module"); + + if (!is_writable(MODPATH)) { + message::warning( + t("The module directory is not writable. Please ensure that it is writable by the web server")); + } + list ($form, $errors) = $this->_get_module_form(); + $view->content->developer_content = $this->_get_module_create_content($form, $errors); + print $view; + } + + public function test_data() { + $v = new Admin_View("admin.html"); + $v->content = new View("admin_developer.html"); + $v->content->title = t("Generate Test Data"); + + list ($form, $errors) = $this->_get_module_form(); + $v->content->developer_content = $this->_get_test_data_view($form, $errors); + print $v; + } + + public function module_create() { + access::verify_csrf(); + + list ($form, $errors) = $this->_get_module_form(); + + $post = new Validation($_POST); + $post->add_rules("name", "required"); + $post->add_rules("display_name", "required"); + $post->add_rules("description", "required"); + $post->add_callbacks("theme", array($this, "_noop_validation")); + $post->add_callbacks("event", array($this, "_noop_validation")); + $post->add_callbacks("name", array($this, "_is_module_defined")); + + if ($post->validate()) { + $task_def = Task_Definition::factory() + ->callback("developer_task::create_module") + ->description(t("Create a new module")) + ->name(t("Create Module")); + $success_msg = t("Generation of %module completed successfully", + array("module" => $post->name)); + $error_msg = t("Generation of %module failed.", array("module" => $post->name)); + $task_context = array("step" => 0, "success_msg" => $success_msg, "error_msg" => $error_msg); + $task = task::create($task_def, array_merge($task_context, $post->as_array())); + + json::reply(array("result" => "started", + "max_iterations" => 15, + "url" => url::site("admin/developer/run_task/{$task->id}?csrf=" . + access::csrf_token()), + "task" => $task->as_array())); + } else { + $v = $this->_get_module_create_content(arr::overwrite($form, $post->as_array()), + arr::overwrite($errors, $post->errors())); + json::reply(array("result" => "error", "html" => (string)$v)); + } + } + + public function _noop_validation(Validation $array, $field) { + } + + public function session($key) { + access::verify_csrf(); + $input = Input::instance(); + Session::instance()->set($key, $input->get("value")); + url::redirect($input->server("HTTP_REFERER")); + } + + public function test_data_create() { + list ($form, $errors) = $this->_get_test_data_form(); + + $post = new Validation($_POST); + $post->add_rules("albums", "numeric"); + $post->add_rules("photos", "numeric"); + $post->add_rules("comments", "numeric"); + $post->add_rules("tags", "numeric"); + $post->add_callbacks("albums", array($this, "_set_default")); + $post->add_callbacks("photos", array($this, "_set_default")); + $post->add_callbacks("comments", array($this, "_set_default")); + $post->add_callbacks("tags", array($this, "_set_default")); + + if ($post->validate()) { + $task_def = Task_Definition::factory() + ->callback("developer_task::create_content") + ->description(t("Create test content")) + ->name(t("Create Test Data")); + $total = $post->albums + $post->photos + $post->comments + $post->tags; + $success_msg = t("Successfully generated test data"); + $error_msg = t("Problems with test data generation was encountered"); + $task = task::create($task_def, array("total" => $total, "batch" => (int)ceil($total / 10), + "success_msg" => $success_msg, + "current" => 0, "error_msg" => $error_msg, + "albums" => $post->albums, "photos" => $post->photos, + "comments" => $post->comments, "tags" => $post->tags)); + batch::start(); + + json::reply(array("result" => "started", + "max_iterations" => $total + 5, + "url" => url::site("admin/developer/run_task/{$task->id}?csrf=" . + access::csrf_token()), + "task" => $task->as_array())); + } else { + $v = $this->_get_test_data_view(arr::overwrite($form, $post->as_array()), + arr::overwrite($errors, $post->errors())); + json::reply(array("result" => "error", "html" => (string)$v)); + } + } + + public function run_task($task_id) { + try { + $task = task::run($task_id); + } catch (Exception $e) { + $error_msg = $e->getMessage(); + $task->done = true; + } + + if ($task->done) { + batch::stop(); + $context = unserialize($task->context); + switch ($task->state) { + case "success": + message::success($context["success_msg"]); + break; + + case "error": + message::success(empty($error_msg) ? $context["error_msg"] : $error_msg); + break; + } + json::reply(array("result" => "success", "task" => $task->as_array())); + + } else { + json::reply(array("result" => "in_progress", "task" => $task->as_array())); + } + } + + function mptt() { + $v = new Admin_View("admin.html"); + $v->content = new View("mptt_tree.html"); + + $v->content->tree = $this->_build_tree(); + + if (exec("which /usr/bin/dot")) { + $v->content->url = url::site("admin/developer/mptt_graph"); + } else { + $v->content->url = null; + message::warning(t("The package 'graphviz' is not installed, degrading to text view")); + } + print $v; + } + + function mptt_graph() { + $items = ORM::factory("item")->order_by("id")->find_all(); + $data = $this->_build_tree(); + + $proc = proc_open("/usr/bin/dot -Tsvg", + array(array("pipe", "r"), + array("pipe", "w")), + $pipes, + VARPATH . "tmp"); + fwrite($pipes[0], $data); + fclose($pipes[0]); + + header("Content-Type: image/svg+xml"); + print(stream_get_contents($pipes[1])); + fclose($pipes[1]); + proc_close($proc); + } + + private function _build_tree() { + $items = ORM::factory("item")->order_by("id")->find_all(); + $data = "digraph G {\n"; + foreach ($items as $item) { + $data .= " $item->parent_id -> $item->id\n"; + $data .= + " $item->id [label=\"$item->id [$item->level] <$item->left_ptr, $item->right_ptr>\"]\n"; + } + $data .= "}\n"; + return $data; + } + + public function _is_module_defined(Validation $post, $field) { + $module_name = strtolower(strtr($post[$field], " ", "_")); + if (file_exists(MODPATH . "$module_name/module.info")) { + $post->add_error($field, "module_exists"); + } + } + + public function _set_default(Validation $post, $field) { + if (empty($post->$field)) { + $post->$field = 0; + } + } + + private function _get_module_form() { + $form = array("name" => "", "display_name" => "", "description" => "", "theme[]" => array(), + "event[]" => array()); + $errors = array_fill_keys(array_keys($form), ""); + + return array($form, $errors); + } + + private function _get_module_create_content($form, $errors) { + $config = Kohana::config("developer.methods"); + + $v = new View("developer_module.html"); + $v->action = "admin/developer/module_create"; + $v->theme = $config["theme"]; + $v->event = $this->_get_events(); + $v->form = $form; + $v->errors = $errors; + $submit_attributes = array( + "id" => "g-generate-module", + "name" => "generate", + "class" => "ui-state-default ui-corner-all", + "style" => "clear:both!important"); + + if (!is_writable(MODPATH)) { + $submit_attributes["class"] .= " ui-state-disabled"; + $submit_attributes["disabled"] = "disabled"; + } + $v->submit_attributes = $submit_attributes; + return $v; + } + + private function _get_events() { + if (empty(self::$event_list)) { + $dir = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator(MODPATH)); + foreach ($dir as $file) { + $file_as_string = file_get_contents($file); + if (preg_match_all('#module::event\("(.*?)"(.*)\);#mU', $file_as_string, $matches, PREG_SET_ORDER) > 0) { + foreach ($matches as $match) { + $event_name = $match[1]; + $display_name = ucwords(str_replace("_", " ", $event_name)); + if (!in_array($display_name, self::$event_list)) { + $parameters = array(); + if (!empty($match[2]) && + preg_match_all('#\$[a-zA-Z_]*#', $match[2], $param_names)) { + + foreach ($param_names[0] as $name) { + $parameters[] = $name != '$this' ? $name : '$' . $event_name; + } + } + self::$event_list["static function $event_name(" . implode(", ", $parameters) . ")"] = $display_name; + } + } + ksort(self::$event_list); + } + } + } + return self::$event_list; + } + + private function _get_test_data_form() { + $form = array("albums" => "10", "photos" => "10", "comments" => "10", "tags" => "10", + "generate_albums" => ""); + $errors = array_fill_keys(array_keys($form), ""); + + return array($form, $errors); + } + + private function _get_test_data_view($form, $errors) { + $v = new View("admin_developer_test_data.html"); + $v->action = "admin/developer/test_data_create"; + $album_count = ORM::factory("item")->where("type", "=", "album")->count_all(); + $photo_count = ORM::factory("item")->where("type", "=", "photo")->count_all(); + + $v->comment_installed = module::is_active("comment"); + $comment_count = empty($v->comment_installed) ? 0 : ORM::factory("comment")->count_all(); + + $v->tag_installed = module::is_active("tag"); + $tag_count = empty($v->tag_installed) ? 0 : ORM::factory("tag")->count_all(); + + $v->album_count = t2("%count album", "%count albums", $album_count); + $v->photo_count = t2("%count photo", "%count photos", $photo_count); + $v->comment_count = t2("%count comment", "%count comments", $comment_count); + $v->tag_count = t2("%count tag", "%count tags", $tag_count); + $v->form = $form; + $v->errors = $errors; + return $v; + } +} diff --git a/modules/developer/data/DSC_0003.jpg b/modules/developer/data/DSC_0003.jpg Binary files differnew file mode 100644 index 0000000..5780d9d --- /dev/null +++ b/modules/developer/data/DSC_0003.jpg diff --git a/modules/developer/data/DSC_0005.jpg b/modules/developer/data/DSC_0005.jpg Binary files differnew file mode 100644 index 0000000..4d2b53a --- /dev/null +++ b/modules/developer/data/DSC_0005.jpg diff --git a/modules/developer/data/DSC_0017.jpg b/modules/developer/data/DSC_0017.jpg Binary files differnew file mode 100644 index 0000000..b7f7bb9 --- /dev/null +++ b/modules/developer/data/DSC_0017.jpg diff --git a/modules/developer/data/DSC_0019.jpg b/modules/developer/data/DSC_0019.jpg Binary files differnew file mode 100644 index 0000000..0ce25aa --- /dev/null +++ b/modules/developer/data/DSC_0019.jpg diff --git a/modules/developer/data/DSC_0067.jpg b/modules/developer/data/DSC_0067.jpg Binary files differnew file mode 100644 index 0000000..84f134c --- /dev/null +++ b/modules/developer/data/DSC_0067.jpg diff --git a/modules/developer/data/DSC_0072.jpg b/modules/developer/data/DSC_0072.jpg Binary files differnew file mode 100644 index 0000000..dfad82b --- /dev/null +++ b/modules/developer/data/DSC_0072.jpg diff --git a/modules/developer/data/P4050088.jpg b/modules/developer/data/P4050088.jpg Binary files differnew file mode 100644 index 0000000..62f4749 --- /dev/null +++ b/modules/developer/data/P4050088.jpg diff --git a/modules/developer/helpers/developer_event.php b/modules/developer/helpers/developer_event.php new file mode 100644 index 0000000..849803f --- /dev/null +++ b/modules/developer/helpers/developer_event.php @@ -0,0 +1,70 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 developer_event_Core { + static function admin_menu($menu, $theme) { + $developer_menu = Menu::factory("submenu") + ->id("developer_menu") + ->label(t("Developer tools")); + $menu->append($developer_menu); + + $developer_menu + ->append(Menu::factory("link") + ->id("generate_menu") + ->label(t("Generate module")) + ->url(url::site("admin/developer/module"))) + ->append(Menu::factory("link") + ->id("generate_data") + ->label(t("Generate test data")) + ->url(url::site("admin/developer/test_data"))) + ->append(Menu::factory("link") + ->id("mptt_tree_menu") + ->label(t("MPTT tree")) + ->url(url::site("admin/developer/mptt"))); + + $csrf = access::csrf_token(); + if (Session::instance()->get("profiler", false)) { + $developer_menu->append( + Menu::factory("link") + ->id("scaffold_profiler") + ->label(t("Profiling off")) + ->url(url::site("admin/developer/session/profiler?value=0&csrf=$csrf"))); + } else { + $developer_menu->append( + Menu::factory("link") + ->id("scaffold_profiler") + ->label(t("Profiling on")) + ->url(url::site("admin/developer/session/profiler?value=1&csrf=$csrf"))); + } + + if (Session::instance()->get("debug", false)) { + $developer_menu->append( + Menu::factory("link") + ->id("scaffold_debugger") + ->label(t("Debugging off")) + ->url(url::site("admin/developer/session/debug?value=0&csrf=$csrf"))); + } else { + $developer_menu->append( + Menu::factory("link") + ->id("scaffold_debugger") + ->label(t("Debugging on")) + ->url(url::site("admin/developer/session/debug?value=1&csrf=$csrf"))); + } + } +} diff --git a/modules/developer/helpers/developer_task.php b/modules/developer/helpers/developer_task.php new file mode 100644 index 0000000..3e27b4b --- /dev/null +++ b/modules/developer/helpers/developer_task.php @@ -0,0 +1,335 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 developer_task_Core { + static function available_tasks() { + // Return empty array so nothing appears in the maintenance screen + return array(); + } + + static function create_module($task) { + $context = unserialize($task->context); + + if (empty($context["module"])) { + $context["class_name"] = strtr($context["name"], " ", "_"); + $context["module"] = strtolower($context["class_name"]); + $context["module_path"] = (MODPATH . $context["module"]); + } + + switch ($context["step"]) { + case 0: // Create directory tree + foreach (array("", "controllers", "helpers", "views") as $dir) { + $path = "{$context['module_path']}/$dir"; + if (!file_exists($path)) { + mkdir($path); + chmod($path, 0755); + } + } + break; + case 1: // Generate installer + $context["installer"] = array(); + self::_render_helper_file($context, "installer"); + break; + case 2: // Generate theme helper + $context["theme"] = !isset($context["theme"]) ? array() : $context["theme"]; + self::_render_helper_file($context, "theme"); + break; + case 3: // Generate block helper + $context["block"] = array(); + self::_render_helper_file($context, "block"); + break; + case 4: // Generate event helper + self::_render_helper_file($context, "event"); + break; + case 5: // Generate admin controller + $file = "{$context['module_path']}/controllers/admin_{$context['module']}.php"; + ob_start(); + $v = new View("admin_controller.txt"); + $v->name = $context["name"]; + $v->module = $context["module"]; + $v->class_name = $context["class_name"]; + print $v->render(); + file_put_contents($file, ob_get_contents()); + ob_end_clean(); + break; + case 6: // Generate admin form + $file = "{$context['module_path']}/views/admin_{$context['module']}.html.php"; + ob_start(); + $v = new View("admin_html.txt"); + $v->name = $context["name"]; + $v->module = $context["module"]; + $v->css_id = preg_replace("#\s+#", "", $context["name"]); + print $v->render(); + file_put_contents($file, ob_get_contents()); + ob_end_clean(); + break; + case 7: // Generate controller + $file = "{$context['module_path']}/controllers/{$context['module']}.php"; + ob_start(); + $v = new View("controller.txt"); + $v->name = $context["name"]; + $v->module = $context["module"]; + $v->class_name = $context["class_name"]; + $v->css_id = preg_replace("#\s+#", "", $context["name"]); + print $v->render(); + file_put_contents($file, ob_get_contents()); + ob_end_clean(); + break; + case 8: // Generate sidebar block view + $file = "{$context['module_path']}/views/{$context['module']}_block.html.php"; + ob_start(); + $v = new View("block_html.txt"); + $v->name = $context["name"]; + $v->module = $context["module"]; + $v->class_name = $context["class_name"]; + $v->css_id = preg_replace("#\s+#", "", $context["name"]); + print $v->render(); + file_put_contents($file, ob_get_contents()); + ob_end_clean(); + break; + case 9: // Generate dashboard block view + $file = "{$context['module_path']}/views/admin_{$context['module']}_block.html.php"; + ob_start(); + $v = new View("dashboard_block_html.txt"); + $v->name = $context["name"]; + $v->module = $context["module"]; + $v->class_name = $context["class_name"]; + $v->css_id = preg_replace("#\s+#", "", $context["name"]); + print $v->render(); + file_put_contents($file, ob_get_contents()); + ob_end_clean(); + break; + case 10: // Generate module.info (do last) + $file = "{$context["module_path"]}/module.info"; + ob_start(); + $v = new View("module_info.txt"); + $v->module_name = $context["display_name"]; + $v->module_description = $context["description"]; + print $v->render(); + file_put_contents($file, ob_get_contents()); + ob_end_clean(); + break; + } + if (isset($file)) { + chmod($file, 0765); + } + $task->done = (++$context["step"]) >= 11; + $task->context = serialize($context); + $task->state = "success"; + $task->percent_complete = ($context["step"] / 11.0) * 100; + } + + private static function _render_helper_file($context, $helper) { + if (isset($context[$helper])) { + $config = Kohana::config("developer.methods"); + $file = "{$context["module_path"]}/helpers/{$context["module"]}_{$helper}.php"; + touch($file); + ob_start(); + $v = new View("$helper.txt"); + $v->helper = $helper; + $v->name = $context["name"]; + $v->module = $context["module"]; + $v->module_name = $context["name"]; + $v->css_id = strtr($context["name"], " ", ""); + $v->css_id = preg_replace("#\s#", "", $context["name"]); + $v->callbacks = empty($context[$helper]) ? array() : array_fill_keys($context[$helper], 1); + print $v->render(); + file_put_contents($file, ob_get_contents()); + ob_end_clean(); + } + } + + static function create_content($task) { + $context = unserialize($task->context); + $batch_cnt = $context["batch"]; + while ($context["albums"] > 0 && $batch_cnt > 0) { + set_time_limit(30); + self::_add_album_or_photo("album"); + + $context["current"]++; + $context["albums"]--; + $batch_cnt--; + } + while ($context["photos"] > 0 && $batch_cnt > 0) { + set_time_limit(30); + self::_add_album_or_photo(); + + $context["current"]++; + $context["photos"]--; + $batch_cnt--; + } + while ($context["comments"] > 0 && $batch_cnt > 0) { + self::_add_comment(); + $context["current"]++; + $context["comments"]--; + $batch_cnt--; + } + while ($context["tags"] > 0 && $batch_cnt > 0) { + self::_add_tag(); + $context["current"]++; + $context["tags"]--; + $batch_cnt--; + } + $task->done = $context["current"] >= $context["total"]; + $task->context = serialize($context); + $task->state = "success"; + $task->percent_complete = $context["current"] / $context["total"] * 100; + } + + private static function _add_album_or_photo($desired_type=null) { + srand(time()); + $parents = ORM::factory("item")->where("type", "=", "album")->find_all()->as_array(); + $owner_id = identity::active_user()->id; + + $test_images = glob(dirname(dirname(__FILE__)) . "/data/*.[Jj][Pp][Gg]"); + + $parent = $parents[array_rand($parents)]; + $parent->reload(); + $type = $desired_type; + if (!$type) { + $type = rand(0, 10) ? "photo" : "album"; + } + if ($type == "album") { + $thumb_size = module::get_var("core", "thumb_size"); + $rand = rand(); + $item = ORM::factory("item"); + $item->type = "album"; + $item->parent_id = $parent->id; + $item->name = "rnd_$rand"; + $item->title = "Rnd $rand"; + $item->description = "random album $rand"; + $item->owner_id = $owner_id; + $parents[] = $item->save(); + } else { + $photo_index = rand(0, count($test_images) - 1); + $item = ORM::factory("item"); + $item->type = "photo"; + $item->parent_id = $parent->id; + $item->set_data_file($test_images[$photo_index]); + $item->name = basename($test_images[$photo_index]); + $item->title = "rnd_" . rand(); + $item->description = "sample thumb"; + $item->owner_id = $owner_id; + $item->save(); + } + } + + private static function _add_comment() { + srand(time()); + $photos = ORM::factory("item")->where("type", "=", "photo")->find_all()->as_array(); + $users = ORM::factory("user")->find_all()->as_array(); + + if (empty($photos)) { + return; + } + + if (module::is_active("akismet")) { + akismet::$test_mode = 1; + } + + $photo = $photos[array_rand($photos)]; + $author = $users[array_rand($users)]; + $guest_name = ucfirst(self::_random_phrase(rand(1, 3))); + $guest_email = sprintf("%s@%s.com", self::_random_phrase(1), self::_random_phrase(1)); + $guest_url = sprintf("http://www.%s.com", self::_random_phrase(1)); + + $comment = ORM::factory("comment"); + $comment->author_id = $author->id; + $comment->item_id = $photo->id; + $comment->text = self::_random_phrase(rand(8, 500)); + $comment->guest_name = $guest_name; + $comment->guest_email = $guest_email; + $comment->guest_url = $guest_url; + $comment->save(); + } + + private static function _add_tag() { + $items = ORM::factory("item")->find_all()->as_array(); + + if (!empty($items)) { + $tags = self::_generateTags(); + + $tag_name = $tags[array_rand($tags)]; + $item = $items[array_rand($items)]; + + tag::add($item, $tag_name); + } + } + + private static function _random_phrase($count) { + static $words; + if (empty($words)) { + $sample_text = "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium + laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi + architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas + sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione + voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, + amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt, ut + labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis + nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi + consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam + nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla + pariatur? At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis + praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi + sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt + mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et + expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque + nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas + assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis + debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et + molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut + reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores + repellat."; + $words = preg_split('/\s+/', $sample_text); + } + + $chosen = array(); + for ($i = 0; $i < $count; $i++) { + $chosen[] = $words[array_rand($words)]; + } + + return implode(' ', $chosen); + } + + private static function _generateTags($number=10){ + // Words from lorem2.com + $words = explode( + " ", + "Lorem ipsum dolor sit amet consectetuer adipiscing elit Donec odio Quisque volutpat " . + "mattis eros Nullam malesuada erat ut turpis Suspendisse urna nibh viverra non " . + "semper suscipit posuere a pede Donec nec justo eget felis facilisis " . + "fermentum Aliquam porttitor mauris sit amet orci Aenean dignissim pellentesque " . + "felis Morbi in sem quis dui placerat ornare Pellentesque odio nisi euismod in " . + "pharetra a ultricies in diam Sed arcu Cras consequat Praesent dapibus neque " . + "id cursus faucibus tortor neque egestas augue eu vulputate magna eros eu " . + "erat Aliquam erat volutpat Nam dui mi tincidunt quis accumsan porttitor " . + "facilisis luctus metus Phasellus ultrices nulla quis nibh Quisque a " . + "lectus Donec consectetuer ligula vulputate sem tristique cursus Nam nulla quam " . + "gravida non commodo a sodales sit amet nisi Pellentesque fermentum " . + "dolor Aliquam quam lectus facilisis auctor ultrices ut elementum vulputate " . + "nunc Sed adipiscing ornare risus Morbi est est blandit sit amet sagittis vel " . + "euismod vel velit Pellentesque egestas sem Suspendisse commodo ullamcorper " . + "magna"); + + while ($number--) { + $results[] = $words[array_rand($words, 1)]; + } + return $results; + } +}
\ No newline at end of file diff --git a/modules/developer/js/developer.js b/modules/developer/js/developer.js new file mode 100644 index 0000000..065a731 --- /dev/null +++ b/modules/developer/js/developer.js @@ -0,0 +1,42 @@ +var module_success = function(data) { + $("#g-developer-admin").append('<div id="g-module-progress" style="margin-top: 1em;"></div>'); + $("#g-module-progress").progressbar(); + + var task = data.task; + var url = data.url; + var done = false; + var counter = 0; + var max_iterations = data.max_iterations; + while (!done) { + $.ajax({async: false, + success: function(data, textStatus) { + $("#g-module-progress").progressbar("value", data.task.percent_complete); + done = data.task.done; + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + done = true; + }, + dataType: "json", + type: "POST", + url: url + }); + // Leave this in as insurance that we never run away + done = done || ++counter > max_iterations; + } + document.location.reload(); +}; + +function ajaxify_developer_form(selector, success) { + $(selector).ajaxForm({ + dataType: "json", + success: function(data) { + if (data.form && data.result != "started") { + $(selector).replaceWith(data.form); + ajaxify_developer_form(selector, success); + } + if (data.result == "started") { + success(data); + } + } + }); +} diff --git a/modules/developer/module.info b/modules/developer/module.info new file mode 100644 index 0000000..b62a6b6 --- /dev/null +++ b/modules/developer/module.info @@ -0,0 +1,7 @@ +name = Developer +description = "Tools to assist module and theme developers" +version = 1 +author_name = "" +author_url = "" +info_url = "http://codex.gallery2.org/Gallery3:Modules:developer" +discuss_url = "http://gallery.menalto.com/forum_module_developer" diff --git a/modules/developer/views/admin_controller.txt.php b/modules/developer/views/admin_controller.txt.php new file mode 100644 index 0000000..879657e --- /dev/null +++ b/modules/developer/views/admin_controller.txt.php @@ -0,0 +1,57 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= "<?php defined(\"SYSPATH\") or die(\"No direct script access.\");" ?> +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 Admin_<?= $class_name ?>_Controller extends Admin_Controller { + public function index() { + print $this->_get_view(); + } + + public function handler() { + access::verify_csrf(); + + $form = $this->_get_form(); + if ($form->validate()) { + // @todo process the admin form + + message::success(t("<?= $name ?> Adminstration Complete Successfully")); + + url::redirect("admin/<?= $module ?>"); + } + + print $this->_get_view($form); + } + + private function _get_view($form=null) { + $v = new Admin_View("admin.html"); + $v->content = new View("admin_<?=$module ?>.html"); + $v->content->form = empty($form) ? $this->_get_form() : $form; + return $v; + } + + private function _get_form() { + $form = new Forge("admin/<?= $module ?>/handler", "", "post", + array("id" => "g-adminForm")); + $group = $form->group("group"); + $group->input("text")->label(t("Text"))->rules("required"); + $group->submit("submit")->value(t("Submit")); + + return $form; + } +}
\ No newline at end of file diff --git a/modules/developer/views/admin_developer.html.php b/modules/developer/views/admin_developer.html.php new file mode 100644 index 0000000..4831f9b --- /dev/null +++ b/modules/developer/views/admin_developer.html.php @@ -0,0 +1,13 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= html::script("modules/developer/js/developer.js") ?> +<script type="text/javascript"> +$("#g-developer-form").ready(function() { + ajaxify_developer_form("#g-developer-form form", module_success); +}); +</script> +<div id="g-developer-admin"> + <h2><?= $title ?></h2> + <div id="g-developer-form" > + <?= $developer_content ?> + </div> +</div> diff --git a/modules/developer/views/admin_developer_test_data.html.php b/modules/developer/views/admin_developer_test_data.html.php new file mode 100644 index 0000000..a29027b --- /dev/null +++ b/modules/developer/views/admin_developer_test_data.html.php @@ -0,0 +1,93 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<script type="text/javascript"> + $("#g-generate-test-data").ready(function() { + $(".g-generate-checkbox").click(function() { + var buttons = $(this).val(); + $(buttons).attr("disabled", !this.checked); + }); + <? if (!empty($form["generate_albums"])): ?> + $("#g-generate-albums").click(); + <? endif ?> + <? if (!empty($form["generate_photos"])): ?> + $("#g-generate-photos").click(); + <? endif ?> + <? if (!empty($form["generate_comments"])): ?> + $("#g-generate-comments").click(); + <? endif ?> + <? if (!empty($form["generate_tags"])): ?> + $("#g-generate-tags").click(); + <? endif ?> + }); +</script> +<?= form::open($action, array("method" => "post", "id" => "g-generate-test-data")) ?> + <? if (!empty($album_count)): ?> + <p><?= t("Currently:") ?><br /> + + <i>(<?= $album_count ?>, <?= $photo_count ?>, <?= $comment_count ?>, <?= $tag_count ?>)</i> + </p> + <? endif ?> + +<fieldset> + <ul> + <li><?= access::csrf_form_field() ?></li> + <li <? if (!empty($errors["albums"])): ?> class="g-error"<? endif ?>> + <fieldset> + <?= form::label("g-generate-albums", t("Generate Albums")) ?> + <?= form::checkbox(array("id" => "g-generate-albums", "name" => "generate_albums", "class" => "g-generate-checkbox", "style" => "display:inline", "checked" => !empty($form["generate_albums"])), ".g-radio-album") ?> + <? foreach (array(1, 10, 50, 100, 500, 1000) as $number): ?> + <span style="float:left;padding-right: .5em;"><?= form::label("album_$number", "$number") ?> + <?= form::radio(array("id" => "album_$number", "name" => "albums", "style" => "display:inline", "checked" => $number == 10, "disabled" => true, "class" => "g-radio-album"), $number) ?></span> + <? endforeach ?> + </fieldset> + <? if (!empty($errors["albums"]) && $errors["albums"] == "numeric"): ?> + <p class="g-error"><?= t("Number to create must be numeric") ?></p> + <? endif ?> + </li> + <li <? if (!empty($errors["photos"])): ?> class="g-error"<? endif ?>> + <fieldset> + <?= form::label("g-generate-photos", t("Generate Photos and Albums")) ?> + <?= form::checkbox(array("id" => "g-generate-photos", "name" => "generate_photos", "class" => "g-generate-checkbox", "style" => "display:inline", "checked" => !empty($form["generate_photos"])), ".g-radio-photo") ?> + <? foreach (array(1, 10, 50, 100, 500, 1000) as $number): ?> + <span style="float:left;padding-right: .5em;"><?= form::label("photo_$number", "$number") ?> + <?= form::radio(array("id" => "photo_$number", "name" => "photos", "style" => "display:inline", "checked" => $number == 10, "disabled" => true, "class" => "g-radio-photo"), $number) ?></span> + <? endforeach ?> + </fieldset> + <? if (!empty($errors["photos"]) && $errors["photos"] == "numeric"): ?> + <p class="g-error"><?= t("Number to create must be numeric") ?></p> + <? endif ?> + </li> + <? if(!empty($comment_installed)): ?> + <li <? if (!empty($errors["comments"])): ?> class="g-error"<? endif ?>> + <fieldset> + <?= form::label("g-generate-comments", t("Generate Comments")) ?> + <?= form::checkbox(array("id" => "g-generate-comments", "name" => "generate_comments", "class" => "g-generate-checkbox", "style" => "display:inline", "checked" => !empty($form["generate_comments"])), ".g-radio-comment") ?> + <? foreach (array(1, 10, 50, 100, 500, 1000) as $number): ?> + <span style="float:left;padding-right: .5em;"><?= form::label("comment_$number", "$number") ?> + <?= form::radio(array("id" => "comment_$number", "name" => "comments", "style" => "display:inline", "checked" => $number == 10, "disabled" => true, "class" => "g-radio-comment"), $number) ?></span> + <? endforeach ?> + </fieldset> + <? if (!empty($errors["comments"]) && $errors["comments"] == "numeric"): ?> + <p class="g-error"><?= t("Number to create must be numeric") ?></p> + <? endif ?> + </li> + <? endif ?> + <? if(!empty($tag_installed)): ?> + <li <? if (!empty($errors["tags"])): ?> class="g-error"<? endif ?>> + <fieldset> + <?= form::label("g-generate-tags", t("Generate Tags")) ?> + <?= form::checkbox(array("id" => "g-generate-tags", "name" => "generate_tags", "class" => "g-generate-checkbox", "style" => "display:inline", "checked" => !empty($form["generate_tags"])), ".g-radio-tag") ?> + <? foreach (array(1, 10, 50, 100, 500, 1000) as $number): ?> + <span style="float:left;padding-right: .5em;"><?= form::label("tag_$number", "$number") ?> + <?= form::radio(array("id" => "tag_$number", "name" => "tags", "style" => "display:inline", "checked" => $number == 10, "disabled" => true, "class" => "g-radio-tag"), $number) ?></span> + <? endforeach ?> + </fieldset> + <? if (!empty($errors["tags"]) && $errors["tags"] == "numeric"): ?> + <p class="g-error"><?= t("Number to create must be numeric") ?></p> + <? endif ?> + </li> + <? endif ?> + <li> + <?= form::submit(array("id" => "g-generate-data", "name" => "generate", "class" => "submit", "style" => "clear:both!important"), t("Generate")) ?> + </li> + </ul> +</fieldset> diff --git a/modules/developer/views/admin_html.txt.php b/modules/developer/views/admin_html.txt.php new file mode 100644 index 0000000..3d230d1 --- /dev/null +++ b/modules/developer/views/admin_html.txt.php @@ -0,0 +1,10 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= "<?php defined(\"SYSPATH\") or die(\"No direct script access.\") ?>" ?> + +<div id="g-admin-<?= $css_id ?>"> + <h2> + <?= "<?= t(\"$name Adminstration\") ?>" ?> + </h2> + <?= "<?= \$form ?>" ?> + +</div> diff --git a/modules/developer/views/block.txt.php b/modules/developer/views/block.txt.php new file mode 100644 index 0000000..fda62c2 --- /dev/null +++ b/modules/developer/views/block.txt.php @@ -0,0 +1,53 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= "<?php defined(\"SYSPATH\") or die(\"No direct script access.\");" ?> + +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 <?= $module ?>_block { + static function get_site_list() { + return array( + "<?= "{$module}_site" ?>" => t("<?= $name ?> Sidebar Block")); + } + + static function get_admin_list() { + return array( + "<?= "{$module}_admin" ?>" => t("<?= $name ?> Dashboard Block")); + } + + static function get($block_id, $theme) { + $block = new Block(); + switch ($block_id) { + case "<?= "{$module}_admin" ?>": + $block->css_id = "g-<?= $css_id ?>-admin"; + $block->title = t("<?= $module ?> Dashboard Block"); + $block->content = new View("admin_<?= $module ?>_block.html"); + + $block->content->item = ORM::factory("item", 1); + break; + case "<?= "{$module}_site" ?>": + $block->css_id = "g-<?= $css_id ?>-site"; + $block->title = t("<?= $module ?> Sidebar Block"); + $block->content = new View("<?= $module ?>_block.html"); + + $block->content->item = ORM::factory("item", 1); + break; + } + return $block; + } +} diff --git a/modules/developer/views/block_html.txt.php b/modules/developer/views/block_html.txt.php new file mode 100644 index 0000000..9942259 --- /dev/null +++ b/modules/developer/views/block_html.txt.php @@ -0,0 +1,10 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= "<?php defined(\"SYSPATH\") or die(\"No direct script access.\") ?>" ?> + +<div class="g-<?= $css_id ?>-block"> + <?= "<a href=\"<?= \$item->url() ?>\">" ?> + + <?= "<?= \$item->thumb_tag(array(\"class\" => \"g-thumbnail\")) ?>" ?> + + </a> +</div> diff --git a/modules/developer/views/controller.txt.php b/modules/developer/views/controller.txt.php new file mode 100644 index 0000000..7c57931 --- /dev/null +++ b/modules/developer/views/controller.txt.php @@ -0,0 +1,50 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= "<?php defined(\"SYSPATH\") or die(\"No direct script access.\");" ?> +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 <?= $class_name ?>_Controller extends Controller { + public function index() { + print $this->_get_form(); + } + + public function handler() { + access::verify_csrf(); + + $form = $this->_get_form(); + if ($form->validate()) { + // @todo process the admin form + + message::success(t("<?= $name ?> Processing Successfully")); + + json::reply(array("result" => "success")); + } else { + json::reply(array("result" => "error", "html" => (string)$form)); + } + } + + private function _get_form() { + $form = new Forge("<?= $module ?>/handler", "", "post", + array("id" => "g-<?= $css_id ?>-form")); + $group = $form->group("group")->label(t("<?= $name ?> Handler")); + $group->input("text")->label(t("Text"))->rules("required"); + $group->submit("submit")->value(t("Submit")); + + return $form; + } +} diff --git a/modules/developer/views/dashboard_block_html.txt.php b/modules/developer/views/dashboard_block_html.txt.php new file mode 100644 index 0000000..8b1c8f5 --- /dev/null +++ b/modules/developer/views/dashboard_block_html.txt.php @@ -0,0 +1,10 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= "<?php defined(\"SYSPATH\") or die(\"No direct script access.\") ?>" ?> + +<div class="g-<?= $css_id ?>-block"> + <?= "<a href=\"<?= \$item->url() ?>\">" ?> + + <?= "<?= \$item->thumb_tag(array(\"class\" => \"g-thumbnail\")) ?>" ?> + + </a> +</div> diff --git a/modules/developer/views/developer_module.html.php b/modules/developer/views/developer_module.html.php new file mode 100644 index 0000000..8552942 --- /dev/null +++ b/modules/developer/views/developer_module.html.php @@ -0,0 +1,49 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> + +<?= form::open($action, array("method" => "post")) ?> + <fieldset> + <ul> + <li><?= access::csrf_form_field() ?></li> + <li <? if (!empty($errors["name"])): ?> class="g-error"<? endif ?>> + <?= form::label("name", t("Name")) ?> + <?= form::input("name", $form["name"]) ?> + <? if (!empty($errors["name"]) && $errors["name"] == "required"): ?> + <p class="g-error"><?= t("Module name is required") ?></p> + <? endif ?> + <? if (!empty($errors["name"]) && $errors["name"] == "module_exists"): ?> + <p class="g-error"><?= t("Module is already implemented") ?></p> + <? endif ?> + </li> + <li <? if (!empty($errors["display_name"])): ?> class="g-error"<? endif ?>> + <?= form::label("display_name", t("Display name")) ?> + <?= form::input("display_name", $form["display_name"]) ?> + <? if (!empty($errors["display_name"]) && $errors["display_name"] == "required"): ?> + <p class="g-error"><?= t("Module display_name is required")?></p> + <? endif ?> + </li> + <li <? if (!empty($errors["description"])): ?> class="g-error"<? endif ?>> + <?= form::label("description", t("Description")) ?> + <?= form::input("description", $form["description"]) ?> + <? if (!empty($errors["description"]) && $errors["description"] == "required"): ?> + <p class="g-error"><?= t("Module description is required")?></p> + <? endif ?> + </li> + <li> + <ul> + <li> + <?= form::label("theme[]", t("Theme callbacks")) ?> + <?= form::dropdown(array("name" => "theme[]", "multiple" => true, "size" => 6), $theme, $form["theme[]"]) ?> + </li> + <li style="padding-left: 1em" > + <?= form::label("event[]", t("Gallery event handlers")) ?> + <?= form::dropdown(array("name" => "event[]", "multiple" => true, "size" => 6), $event, $form["event[]"]) ?> + </li> + </ul> + </li> + <li> + <?= form::submit($submit_attributes, t("Generate")) ?> + </li> + </ul> + </fieldset> +</form> + diff --git a/modules/developer/views/event.txt.php b/modules/developer/views/event.txt.php new file mode 100644 index 0000000..1519de2 --- /dev/null +++ b/modules/developer/views/event.txt.php @@ -0,0 +1,27 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= "<?php defined(\"SYSPATH\") or die(\"No direct script access.\");" ?> +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 <?= $module ?>_event { +<? foreach ($callbacks as $callback => $unused): ?> + <?= $callback ?> { + } + +<? endforeach ?> +} diff --git a/modules/developer/views/installer.txt.php b/modules/developer/views/installer.txt.php new file mode 100644 index 0000000..3c78272 --- /dev/null +++ b/modules/developer/views/installer.txt.php @@ -0,0 +1,37 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= "<?php defined(\"SYSPATH\") or die(\"No direct script access.\");" ?> +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 <?= $module ?>_installer { + static function install() { + $version = module::get_version("<?= $module ?>"); + if ($version == 0) { + /* @todo Put database creation here */ + module::set_version("<?= $module ?>", 1); + } + } + + static function upgrade($version) { + } + + static function uninstall() { + /* @todo Put database table drops here */ + module::delete("<?= $module ?>"); + } +} diff --git a/modules/developer/views/module_info.txt.php b/modules/developer/views/module_info.txt.php new file mode 100644 index 0000000..0ea06e7 --- /dev/null +++ b/modules/developer/views/module_info.txt.php @@ -0,0 +1,6 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +name = <?= $module_name ?> + +description = "<?= $module_description ?>" + +version = 1 diff --git a/modules/developer/views/mptt_tree.html.php b/modules/developer/views/mptt_tree.html.php new file mode 100644 index 0000000..db6d4ff --- /dev/null +++ b/modules/developer/views/mptt_tree.html.php @@ -0,0 +1,15 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="g-mptt-tree"> + <h2> + <?= t("MPTT Tree Visualizer") ?> + </h2> + <div id="g-mptt"> + <? if (empty($url)): ?> + <pre><?= $tree ?></pre> + <? else: ?> + <object type="image/svg+xml" data="<?= $url ?>" style="width: 100%; height: 100%;" > + <pre><?= $tree ?></pre> + </object> + <? endif ?> + </div> +</div> diff --git a/modules/developer/views/theme.txt.php b/modules/developer/views/theme.txt.php new file mode 100644 index 0000000..60be3b5 --- /dev/null +++ b/modules/developer/views/theme.txt.php @@ -0,0 +1,157 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= "<?php defined(\"SYSPATH\") or die(\"No direct script access.\");" ?> +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 <?= $module ?>_theme { +<? if (!empty($callbacks["album_blocks"])): ?> + static function album_blocks($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["album_bottom"])): ?> + static function album_bottom($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["album_top"])): ?> + static function album_top($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["admin_credits"])): ?> + static function admin_credits($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["photo"])): ?> + static function admin_footer($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["admin_header_top"])): ?> + static function admin_header_top($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["admin_header_bottom"])): ?> + static function admin_header_bottom($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["admin_page_bottom"])): ?> + static function admin_page_bottom($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["admin_page_top"])): ?> + static function admin_page_top($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["admin_head"])): ?> + static function admin_head($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["credits"])): ?> + static function credits($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["dynamic_bottom"])): ?> + static function dynamic_bottom($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["dynamic_top"])): ?> + static function dynamic_top($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["footer"])): ?> + static function footer($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["head"])): ?> + static function head($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["header_bottom"])): ?> + static function header_bottom($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["header_top"])): ?> + static function header_top($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["page_bottom"])): ?> + static function page_bottom($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["pae_top"])): ?> + static function page_top($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["photo_blocks"])): ?> + static function photo_blocks($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["photo_bottom"])): ?> + static function photo_bottom($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["photo_top"])): ?> + static function photo_top($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["sidebar_bottom"])): ?> + static function sidebar_bottom($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["sidebar_top"])): ?> + static function sidebar_top($theme) { + } + +<? endif ?> +<? if (!empty($callbacks["thumb_bottom"])): ?> + static function thumb_bottom($theme, $child) { + } + +<? endif ?> +<? if (!empty($callbacks["thumb_info"])): ?> + static function thumb_info($theme, $child) { + } + +<? endif ?> +<? if (!empty($callbacks["thumb_top"])): ?> + static function thumb_top($theme, $child) { + } + +<? endif ?> +} diff --git a/modules/keeporiginal/controllers/keeporiginal.php b/modules/keeporiginal/controllers/keeporiginal.php new file mode 100644 index 0000000..da401bd --- /dev/null +++ b/modules/keeporiginal/controllers/keeporiginal.php @@ -0,0 +1,72 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 keeporiginal_Controller extends Controller { + + public function restore($id) { + // Allow the user to restore the original photo. + + // Make sure the current user has suficient access to view and edit the item. + $item = ORM::factory("item", $id); + access::required("view", $item); + access::required("edit", $item); + + // Figure out where the original was stashed at. + $original_image = VARPATH . "original/" . str_replace(VARPATH . "albums/", "", $item->file_path()); + + // Make sure the current item is a photo and that an original exists. + if ($item->is_photo() && file_exists($original_image)) { + // Delete the modified version of the photo. + @unlink($item->file_path()); + + // Copy the original image back over, display an error message if the copy fails. + if (@rename($original_image, $item->file_path())) { + // Re-generate the items resize and thumbnail. + $item_data = model_cache::get("item", $id); + $item_data->resize_dirty= 1; + $item_data->thumb_dirty= 1; + $item_data->save(); + graphics::generate($item_data); + + // If the item is the thumbnail for the parent album, + // fix the parent's thumbnail as well. + $parent = $item_data->parent(); + if ($parent->album_cover_item_id == $item_data->id) { + copy($item_data->thumb_path(), $parent->thumb_path()); + $parent->thumb_width = $item_data->thumb_width; + $parent->thumb_height = $item_data->thumb_height; + $parent->save(); + } + + // Display a success message and redirect to the items page. + message::success(t("Your original image has been restored.")); + url::redirect($item->url()); + + } else { + // Display an error message if the copy failed. + message::error(t("Image restore failed!")); + url::redirect($item->url()); + } + } else { + // Display an error message if there is not an original photo. + message::error(t("Image restore failed!")); + url::redirect($item->url()); + } + } +} diff --git a/modules/keeporiginal/helpers/keeporiginal_event.php b/modules/keeporiginal/helpers/keeporiginal_event.php new file mode 100644 index 0000000..3b325f3 --- /dev/null +++ b/modules/keeporiginal/helpers/keeporiginal_event.php @@ -0,0 +1,139 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 keeporiginal_event_Core { + static function graphics_rotate($input_file, $output_file, $options) { + // Make a copy of the original fullsized image before rotating it. + keeporiginal_event_Core::_preserve($input_file); + } + + static function _preserve($input_file) { + // If $input_file is located in VARPATH/albums/ then assume its a fullsize photo. + if (strncmp($input_file, VARPATH . "albums/", strlen(VARPATH . "albums/")) == 0) { + // Figure out where the original copy should be stashed at. + $temp_path = str_replace(VARPATH . "albums/", "", $input_file); + $original_image = VARPATH . "original/" . $temp_path; + $individual_dirs = preg_split("|[/\\\\]|", "original/" . $temp_path); + // If any original file does not already exist, then create a folder structure + // similar to that found in VARPATH/albums/ and copy the photo over before + // rotating it. + if (!file_exists($original_image)) { + $new_img_path = VARPATH; + for($i = 0; $i < count($individual_dirs)-1; $i++) { + $new_img_path = $new_img_path . "/" . $individual_dirs[$i]; + if(!file_exists($new_img_path)) { + @mkdir($new_img_path); + } + } + if (!@copy($input_file, $original_image)) { + // If the copy failed, display an error message. + message::error(t("Your original image was not backed up!")); + } + } + } + } + + static function item_before_delete($item) { + // If deleting a photo, make sure the original is deleted as well, if it exists. + if ($item->is_photo()) { + $original_file = VARPATH . "original/" . str_replace(VARPATH . "albums/", "", $item->file_path()); + if (file_exists($original_file)) { + @unlink($original_file); + } + } + + // When deleting an album, make sure its corresponding location in + // VARPATH/original/ is deleted as well, if it exists. + if ($item->is_album()) { + $original_file = VARPATH . "original/" . str_replace(VARPATH . "albums/", "", $item->file_path()); + if (file_exists($original_file)) { + @dir::unlink($original_file); + } + } + } + + static function item_updated($old, $new) { + // When updating an item, check and see if the file name is being changed. + // If so, check for and modify any corresponding file/folder in + // VARPATH/original/ as well. + + if ($old->is_photo() || $old->is_album()) { + $data_file = $new->data_file; + if (isset($data_file)) { + keeporiginal_event_Core::_preserve($old->file_path()); + } + if ($old->file_path() != $new->file_path()) { + $old_original = VARPATH . "original/" . str_replace(VARPATH . "albums/", "", $old->file_path()); + $new_original = VARPATH . "original/" . str_replace(VARPATH . "albums/", "", $new->file_path()); + if (file_exists($old_original)) { + @rename($old_original, $new_original); + } + } + } + } + + static function item_moved($item, $old_parent) { + // When moving an item, check and see if a corresponding file exists + // in VARPATH/original/. If so, move that item to a similar directory + // in original as well. + + if ($item->is_photo() || $item->is_album()) { + $old_item_path = $old_parent->file_path() . "/" . $item->name; + if ($item->file_path() != $old_item_path) { + $old_original = VARPATH . "original/" . str_replace(VARPATH . "albums/", "", $old_item_path); + $new_original = VARPATH . "original/" . str_replace(VARPATH . "albums/", "", $item->file_path()); + + if (file_exists($old_original)) { + + // Make sure the new folder exists, create it if it doesn't. + $individual_dirs = split("[/\]", "original/" . str_replace(VARPATH . "albums/", "", $item->file_path())); + $new_img_path = VARPATH; + for($i = 0; $i < count($individual_dirs)-1; $i++) { + $new_img_path = $new_img_path . "/" . $individual_dirs[$i]; + if(!file_exists($new_img_path)) { + @mkdir($new_img_path); + } + } + + // Move the file to its new location. + // TODO: If the files have different extensions, then the old extension should be preserved. + @rename($old_original, $new_original); + } + } + } + } + + static function site_menu($menu, $theme) { + // Create a menu option to restore the original photo. + if ($item = $theme->item()) { + if ((access::can("view", $item)) && (access::can("edit", $item))) { + $original_image = VARPATH . "original/" . str_replace(VARPATH . "albums/", "", $item->file_path()); + + if ($item->is_photo() && file_exists($original_image)) { + $menu->get("options_menu") + ->append(Menu::factory("link") + ->id("restore") + ->label(t("Restore original")) + ->css_id("g-keep-originals-link") + ->url(url::site("keeporiginal/restore/" . $item->id))); + } + } + } + } +}
\ No newline at end of file diff --git a/modules/keeporiginal/helpers/keeporiginal_installer.php b/modules/keeporiginal/helpers/keeporiginal_installer.php new file mode 100644 index 0000000..d2f0d26 --- /dev/null +++ b/modules/keeporiginal/helpers/keeporiginal_installer.php @@ -0,0 +1,25 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 keeporiginal_installer { + static function install() { + @mkdir(VARPATH . "original"); + module::set_version("keeporiginal", 1); + } +} diff --git a/modules/keeporiginal/module.info b/modules/keeporiginal/module.info new file mode 100644 index 0000000..5c7b180 --- /dev/null +++ b/modules/keeporiginal/module.info @@ -0,0 +1,7 @@ +name = "Keep Original" +description = "Make a copy of the original photo before rotating it." +version = 1 +author_name = "" +author_url = "" +info_url = "http://codex.gallery2.org/Gallery3:Modules:keeporiginal" +discuss_url = "http://gallery.menalto.com/forum_module_keeporiginal" diff --git a/modules/latestupdates/controllers/latestupdates.php b/modules/latestupdates/controllers/latestupdates.php new file mode 100644 index 0000000..fb9a8f2 --- /dev/null +++ b/modules/latestupdates/controllers/latestupdates.php @@ -0,0 +1,596 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 latestupdates_Controller extends Controller { + public function user_profiles($str_display_type, $user_id) { + // Make sure user_id is valid, throw a 404 error if its not. + $current_user = ORM::factory("user", $user_id); + if (!$current_user->loaded()) { + throw new Kohana_404_Exception(); + } + + // Grab the first 10 items for the specified display type. + // Default to "popular" if display type is invalid. + $template = new View("latestupdates_user_profile_carousel.html"); + $template->items = latestupdates_Controller::items($str_display_type, $user_id, 10); + + // Figure out the text for the "View more" link. + if ($str_display_type == "recent") { + $template->str_view_more_title = t("View all recent uploads"); + } elseif ($str_display_type == "albums") { + $template->str_view_more_title = t("View all recent albums"); + } else { + $template->str_view_more_title = t("View more popular uploads"); + } + + // Set up a "View more" url. + $template->str_view_more_url = url::site("latestupdates/users/{$str_display_type}/{$user_id}"); + + // Display the page. + print $template; + + // Make item links in the carousel load as virtual albums for the view type instead of the regular album. + item::set_display_context_callback("latestupdates_Controller::get_display_context", + $str_display_type, $user_id); + return ; + } + + public function users($str_display_type, $user_id) { + // Generate a dynamic page with items uploaded by a specific user ($user_id). + + // Make sure user_id is valid. + $current_user = ORM::factory("user", $user_id); + if (!$current_user->loaded()) { + throw new Kohana_404_Exception(); + } + + // Figure out how many items to display on each page. + $page_size = module::get_var("gallery", "page_size", 9); + + // Figure out which page # the visitor is on and + // don't allow the visitor to go below page 1. + $page = Input::instance()->get("page", 1); + if ($page < 1) { + url::redirect("latestupdates/users/{$str_display_type}/{$user_id}"); + } + + // If this page was reached from a breadcrumb, figure out what page to load from the show id. + $show = Input::instance()->get("show"); + if ($show) { + $child = ORM::factory("item", $show); + $index = latestupdates_Controller::_get_position($child, $str_display_type, $user_id); + if ($index) { + $page = ceil($index / $page_size); + if ($page == 1) { + url::redirect("latestupdates/users/{$str_display_type}/{$user_id}"); + } else { + url::redirect("latestupdates/users/{$str_display_type}/{$user_id}?page=$page"); + } + } + } + + // First item to display. + $offset = ($page - 1) * $page_size; + + // Determine the total number of items, + // for page numbering purposes. + $count = latestupdates_Controller::items_count($str_display_type, $user_id); + + // Figure out what the highest page number is. + $max_pages = ceil($count / $page_size); + + // Don't let the visitor go past the last page. + if ($max_pages && $page > $max_pages) { + url::redirect("latestupdates/users/{$str_display_type}/{$user_id}?page=$max_pages"); + } + + // Figure out which items to display on this page. + $children = latestupdates_Controller::items($str_display_type, $user_id, $page_size, $offset); + + // Figure out the page title. + $str_page_title = ""; + if ($str_display_type == "recent") { + $str_page_title = t("Recent Uploads"); + } elseif ($str_display_type == "albums") { + $str_page_title = t("Recent Albums"); + } else { + $str_page_title = t("Most Viewed"); + } + + // Set up the previous and next page buttons. + if ($page > 1) { + $previous_page = $page - 1; + $view->previous_page_link = url::site("latestupdates/users/{$str_display_type}/{$user_id}?page={$previous_page}"); + } + if ($page < $max_pages) { + $next_page = $page + 1; + $view->next_page_link = url::site("latestupdates/users/{$str_display_type}/{$user_id}?page={$next_page}"); + } + + // Set up and display the actual page. + $root = item::root(); + $template = new Theme_View("page.html", "collection", "LatestUpdates"); + $template->page_title = t("Gallery :: Latest Updates"); + $template->set_global( + array("page" => $page, + "max_pages" => $max_pages, + "page_size" => $page_size, + "children" => $children, + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance(t("User profile: %name", array("name" => $current_user->display_name())), + url::site("user_profile/show/{$user_id}")), + Breadcrumb::instance($str_page_title, + url::site("latestupdates/users/{$str_display_type}/{$user_id}"))->set_last()), + "children_count" => $count)); + $template->content = new View("dynamic.html"); + $template->content->title = $str_page_title; + + // Display the page. + print $template; + + // Set up the callback so links within the photo page will lead to photos within the virtual album + // instead of the actual album. + item::set_display_context_callback("latestupdates_Controller::get_display_context", + $str_display_type, $user_id); + } + + public function albums($id) { + // Figure out how many items to display on each page. + $page_size = module::get_var("gallery", "page_size", 9); + + // Load the parent album. + $item = ORM::factory("item", $id); + + // Figure out which page # the visitor is on and + // don't allow the visitor to go below page 1. + $page = Input::instance()->get("page", 1); + if ($page < 1) { + url::redirect("latestupdates/albums/{$item->id}"); + } + + // If this page was reached from a breadcrumb, figure out what page to load from the show id. + $show = Input::instance()->get("show"); + if ($show) { + $child = ORM::factory("item", $show); + $index = latestupdates_Controller::_get_position($child, "descendants", $item->id); + if ($index) { + $page = ceil($index / $page_size); + if ($page == 1) { + url::redirect("latestupdates/albums/{$item->id}"); + } else { + url::redirect("latestupdates/albums/{$item->id}?page=$page"); + } + } + } + + // First item to display. + $offset = ($page - 1) * $page_size; + + // Determine the total number of items, + // for page numbering purposes. + $count = latestupdates_Controller::items_count("descendants", $item->id); + + // Figure out what the highest page number is. + $max_pages = ceil($count / $page_size); + + // Don't let the visitor go past the last page. + if ($max_pages && $page > $max_pages) { + url::redirect("latestupdates/albums/{$item->id}?page=$max_pages"); + } + + // Figure out which items to display on this page. + $children = latestupdates_Controller::items("descendants", $item->id, $page_size, $offset); + + // Set up the previous and next page buttons. + if ($page > 1) { + $previous_page = $page - 1; + $view->previous_page_link = url::site("latestupdates/albums/{$item->id}?page={$previous_page}"); + } + if ($page < $max_pages) { + $next_page = $page + 1; + $view->next_page_link = url::site("latestupdates/albums/{$item->id}?page={$next_page}"); + } + + // Set up breadcrumbs. + $breadcrumbs = array(); + $counter = 0; + $breadcrumbs[] = Breadcrumb::instance(t("Recent Uploads"), url::site("latestupdates/albums/{$item->id}"))->set_last(); + $parent_item = $item; + while ($parent_item->id != 1) { + $breadcrumbs[] = Breadcrumb::instance($parent_item->title, $parent_item->url()); + $parent_item = ORM::factory("item", $parent_item->parent_id); + } + $breadcrumbs[] = Breadcrumb::instance($parent_item->title, $parent_item->url())->set_first(); + $breadcrumbs = array_reverse($breadcrumbs, true); + + // Set up and display the actual page. + $root = item::root(); + $template = new Theme_View("page.html", "collection", "LatestUpdates"); + $template->page_title = t("Gallery :: Latest Updates"); + $template->set_global( + array("page" => $page, + "max_pages" => $max_pages, + "page_size" => $page_size, + "children" => $children, + "breadcrumbs" => $breadcrumbs, + "children_count" => $count)); + $template->content = new View("dynamic.html"); + $template->content->title = t("Recent Uploads"); + + // Display the page. + print $template; + + // Set up the callback so links within the photo page will lead to photos within the virtual album + // instead of the actual album. + item::set_display_context_callback("latestupdates_Controller::get_display_context", + "descendants", $item->id); + } + + public function updates() { + // Figure out how many items to display on each page. + $page_size = module::get_var("gallery", "page_size", 9); + + // Figure out which page # the visitor is on and + // don't allow the visitor to go below page 1. + $page = Input::instance()->get("page", 1); + if ($page < 1) { + url::redirect("latestupdates/updates"); + } + + // If this page was reached from a breadcrumb, figure out what page to load from the show id. + $show = Input::instance()->get("show"); + if ($show) { + $child = ORM::factory("item", $show); + $index = latestupdates_Controller::_get_position($child, "recent", 0); + if ($index) { + $page = ceil($index / $page_size); + if ($page == 1) { + url::redirect("latestupdates/updates"); + } else { + url::redirect("latestupdates/updates?page=$page"); + } + } + } + + // First item to display. + $offset = ($page - 1) * $page_size; + + // Determine the total number of items, + // for page numbering purposes. + $count = latestupdates_Controller::items_count("recent", 0); + + // Figure out what the highest page number is. + $max_pages = ceil($count / $page_size); + + // Don't let the visitor go past the last page. + if ($max_pages && $page > $max_pages) { + url::redirect("latestupdates/updates?page=$max_pages"); + } + + // Figure out which items to display on this page. + $items = latestupdates_Controller::items("recent", 0, $page_size, $offset); + + // Set up the previous and next page buttons. + if ($page > 1) { + $previous_page = $page - 1; + $view->previous_page_link = url::site("latestupdates/updates?page={$previous_page}"); + } + if ($page < $max_pages) { + $next_page = $page + 1; + $view->next_page_link = url::site("latestupdates/updates?page={$next_page}"); + } + + // Set up and display the actual page. + $root = item::root(); + $template = new Theme_View("page.html", "collection", "LatestUpdates"); + $template->page_title = t("Gallery :: Latest Updates"); + $template->set_global( + array("page" => $page, + "max_pages" => $max_pages, + "page_size" => $page_size, + "children" => $items, + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance(t("Recent Uploads"), + url::site("latestupdates/updates"))->set_last()), + "children_count" => $count)); + $template->content = new View("dynamic.html"); + $template->content->title = t("Recent Uploads"); + + // Display the page. + print $template; + + // Set up the callback so links within the photo page will lead to photos within the virtual album + // instead of the actual album. + item::set_display_context_callback("latestupdates_Controller::get_display_context", + "recent", 0); + } + + static function get_display_context($item, $str_display_type, $user_id) { + // Set up display elements on the photo page to link to the virtual album. + // Valid $str_display_type values are popular, recent, albums and descendants. + // $user_id can be set to "0" to search site wide. + // For "descendants", $user_id should be the album id #. + + // Figure out page title. + $str_page_title = ""; + if ($str_display_type == "recent") { + $str_page_title = t("Recent Uploads"); + } elseif ($str_display_type == "albums") { + $str_page_title = t("Recent Albums"); + } elseif ($str_display_type == "descendants") { + $str_page_title = t("Recent Uploads"); + } else { + $str_page_title = t("Most Viewed"); + } + + // Figure out item position. + $position = latestupdates_Controller::_get_position($item, $str_display_type, $user_id); + + // Figure out which items are the previous and next items with the virtual album. + if ($position > 1) { + list ($previous_item, $ignore, $next_item) = + latestupdates_Controller::items($str_display_type, $user_id, 3, $position - 2); + } else { + $previous_item = null; + list ($next_item) = latestupdates_Controller::items($str_display_type, $user_id, 1, $position); + } + + // Figure out total number of items (excluding albums). + $count = latestupdates_Controller::items_count($str_display_type, $user_id); + + // Set up breadcrumbs. + $root = item::root(); + $breadcrumbs = array(); + if ($user_id == 0) { + $breadcrumbs[0] = Breadcrumb::instance($root->title, $root->url())->set_first(); + $breadcrumbs[1] = Breadcrumb::instance($str_page_title, + url::site("latestupdates/updates?show={$item->id}")); + $breadcrumbs[2] = Breadcrumb::instance($item->title, $item->url())->set_last(); + } else { + if ($str_display_type == "descendants") { + $counter = 0; + $breadcrumbs[] = Breadcrumb::instance($item->title, $item->url())->set_last(); + $breadcrumbs[] = Breadcrumb::instance(t("Recent Uploads"), url::site("latestupdates/albums/{$user_id}?show={$item->id}")); + $parent_item = ORM::factory("item", $user_id); + while ($parent_item->id != 1) { + $breadcrumbs[] = Breadcrumb::instance($parent_item->title, $parent_item->url()); + $parent_item = ORM::factory("item", $parent_item->parent_id); + } + $breadcrumbs[] = Breadcrumb::instance($parent_item->title, $parent_item->url())->set_first(); + $breadcrumbs = array_reverse($breadcrumbs, true); + } else { + $current_user = ORM::factory("user", $user_id); + $breadcrumbs[0] = Breadcrumb::instance($root->title, $root->url())->set_first(); + $breadcrumbs[1] = Breadcrumb::instance(t("User profile: %name", array("name" => $current_user->display_name())), + url::site("user_profile/show/{$user_id}")); + $breadcrumbs[2] = Breadcrumb::instance($str_page_title, + url::site("latestupdates/users/{$str_display_type}/{$user_id}?show={$item->id}")); + $breadcrumbs[3] = Breadcrumb::instance($item->title, $item->url())->set_last(); + } + } + + // Return the display elements. + return array("position" => $position, + "previous_item" => $previous_item, + "next_item" => $next_item, + "sibling_count" => $count, + "siblings_callback" => array("latestupdates_Controller::items", array($str_display_type, $user_id)), + "breadcrumbs" => $breadcrumbs + ); + } + + static function items_count($str_display_type, $user_id) { + // Figure out the total number of items. + // Valid $str_display_type values are popular, recent, albums and descendants. + // $user_id can be set to "0" to search site wide. + // For "descendants", $user_id should be the album id #. + + // If $str_display_type is albums, then we only want albums. + // If it's not, then we want everything except albums. + if ($str_display_type == "albums") { + // This is only used for user profiles, so we always want + // results from a specific user. + $count = ORM::factory("item") + ->viewable() + ->where("type", "=", "album") + ->where("owner_id", "=", $user_id) + ->count_all(); + } else { + + // If $user_id is not 0 we only want results from a specific user, + // Or else we want results from any user. + if ($user_id == 0) { + $count = ORM::factory("item") + ->viewable() + ->where("type", "!=", "album") + ->count_all(); + } else { + + // If type is descendants, then user_id is actually an item id#. + if ($str_display_type == "descendants") { + $item = ORM::factory("item", $user_id); + $count = $item + ->viewable() + ->where("type", "!=", "album") + ->order_by("created", "DESC") + ->descendants_count(); + } else { + $count = ORM::factory("item") + ->viewable() + ->where("type", "!=", "album") + ->where("owner_id", "=", $user_id) + ->count_all(); + } + } + } + + return $count; + } + + static function items($str_display_type, $user_id, $limit=null, $offset=null) { + // Query the database for a list of items to display in the virtual album. + // Valid $str_display_type values are popular, recent, albums and descendants. + // $user_id can be set to "0" to search site wide. + // For "descendants", $user_id should be the album id #. + + // Figure out search parameters based on $str_display_type. + $str_where = array(); + $str_orderby_field = ""; + if ($str_display_type == "recent") { + $str_where = array(array("type", "!=", "album")); + $str_orderby_field = "created"; + } elseif ($str_display_type == "albums") { + $str_where = array(array("type", "=", "album")); + $str_orderby_field = "created"; + } else { + $str_where = array(array("type", "!=", "album")); + $str_orderby_field = "view_count"; + } + + // Search the database for matching items. + + // Searching for descendants of a parent album is significantly + // different from the other query types, so we're doing this one + // seperately. + if ($str_display_type == "descendants") { + $item = ORM::factory("item", $user_id); + return $item + ->viewable() + ->where("type", "!=", "album") + ->order_by("created", "DESC") + ->descendants($limit, $offset); + } + + // If $user_id is greater then 0, limit results + // to a specific user. + if ($user_id == 0) { + return ORM::factory("item") + ->viewable() + ->merge_where($str_where) + ->order_by($str_orderby_field, "DESC") + ->find_all($limit, $offset); + } else { + return ORM::factory("item") + ->viewable() + ->merge_where($str_where) + ->where("owner_id", "=", $user_id) + ->order_by($str_orderby_field, "DESC") + ->find_all($limit, $offset); + } + } + + private function _get_position($item, $str_display_type, $user_id) { + // Figure out the item's position within the virtual album. + // Valid $str_display_type values are popular, recent, albums and descendants. + // $user_id can be set to "0" to search site wide. + // For "descendants", $user_id should be the album id #. + + // Figure out search conditions. + $str_where = array(); + $str_orderby_field = ""; + if ($str_display_type == "recent") { + $str_where = array(array("type", "!=", "album")); + $str_orderby_field = "created"; + } elseif ($str_display_type == "albums") { + $str_where = array(array("type", "=", "album")); + $str_orderby_field = "created"; + } else { + $str_where = array(array("type", "!=", "album")); + $str_orderby_field = "view_count"; + } + + // Count the number of records that have a higher orderby_field value then + // the item we're looking for. + $position = 0; + if ($user_id == 0) { + $position = ORM::factory("item") + ->viewable() + ->merge_where($str_where) + ->where($str_orderby_field, ">", $item->$str_orderby_field) + ->order_by($str_orderby_field, "DESC") + ->count_all(); + } else { + if ($str_display_type == "descendants") { + $album_item = ORM::factory("item", $user_id); + $position = $album_item + ->viewable() + ->where("type", "!=", "album") + ->where("created", ">", $item->created) + ->order_by("created", "DESC") + ->descendants_count(); + } else { + $position = ORM::factory("item") + ->viewable() + ->where("owner_id", "=", $user_id) + ->merge_where($str_where) + ->where($str_orderby_field, ">", $item->$str_orderby_field) + ->order_by($str_orderby_field, "DESC") + ->count_all(); + } + } + + // Set up a db query for all records with the same orderby field value + // as the item we're looking for. + $items = ORM::factory("item"); + if ($user_id == 0) { + $items->viewable() + ->merge_where($str_where) + ->where($str_orderby_field, "=", $item->$str_orderby_field) + ->order_by($str_orderby_field, "DESC"); + } else { + if ($str_display_type == "descendants") { + $item_album = ORM::factory("item", $user_id); + $items = $item_album + ->viewable() + ->where("type", "!=", "album") + ->where("created", "=", $item->created) + ->order_by("created", "DESC"); + } else { + $items->viewable() + ->where("owner_id", "=", $user_id) + ->merge_where($str_where) + ->where($str_orderby_field, "=", $item->$str_orderby_field) + ->order_by($str_orderby_field, "DESC"); + } + } + + // Loop through each remaining match, increasing position by 1 each time + // until we find a match. + if ($str_display_type == "descendants") { + foreach ($items->descendants() as $row) { + $position++; + if ($row->id == $item->id) { + break; + } + } + } else { + foreach ($items->find_all() as $row) { + $position++; + if ($row->id == $item->id) { + break; + } + } + } + + // Return the result. + return $position; + } +} diff --git a/modules/latestupdates/css/latestupdates_jcarousel.css b/modules/latestupdates/css/latestupdates_jcarousel.css new file mode 100644 index 0000000..f123143 --- /dev/null +++ b/modules/latestupdates/css/latestupdates_jcarousel.css @@ -0,0 +1,95 @@ +#jCarouselLite .carousel { + padding: 10px 0 0 0; + margin: 0 0 20px 10px; + position: relative; +} + +#jCarouselLite .digg { + position: absolute; + left: 610px; + top: 110px; +} + +#jCarouselLite .main { + margin-left: 0px; +} + +#jCarouselLite .demo em { + color: #FF3300; + font-weight: bold; + font-size: 60%; + font-style: normal; +} + +#jCarouselLite .carousel a.prev, #jCarouselLite .carousel a.next { + display: block; + float: left; + width: 30px; + height: 90px; + text-decoration: none; + background: url("../images/imageNavLeft.gif") left 60px no-repeat; +} + +#jCarouselLite .carousel a.next { + background: url("../images/imageNavRight.gif") right 60px no-repeat; +} + +#jCarouselLite .carousel a.next:hover { + background-image: url("../images/imageNavRightHover.gif"); +} + +#jCarouselLite .carousel a.prev:hover { + background-image: url("../images/imageNavLeftHover.gif"); +} + +#jCarouselLite .carousel a:hover, #jCarouselLite .carousel a:active { + border: none; + outline: none; +} + +#jCarouselLite .carousel .jCarouselLite { + border: 1px solid black; + float: left; + background-color: #dfdfdf; + + /* Needed for rendering without flicker */ + position: relative; + visibility: hidden; + left: -5000px; +} + +#jCarouselLite .carousel ul { + margin: 0; +} + +#jCarouselLite .carousel li img, +#jCarouselLite .carousel li p { + background-color: #fff; + margin: 10px; +} + +#jCarouselLite .widget img { + cursor: pointer; +} + +#jCarouselLite .mid { + margin-left: 80px; + width: 400px; + height: 300px; +} + +#jCarouselLite .vertical { + margin-left: 90px; +} + +#jCarouselLite .vertical .jCarouselLite { /* so that in IE 6, the carousel div doesnt expand to fill the space */ + width: 90px; +} + +#jCarouselLite .imageSlider li img, +#jCarouselLite .imageSlider li p, +#jCarouselLite .imageSliderExt li img , +#jCarouselLite .imageSliderExt li p { + width: 400px; + height: 300px; +} diff --git a/modules/latestupdates/helpers/latestupdates_block.php b/modules/latestupdates/helpers/latestupdates_block.php new file mode 100644 index 0000000..b15b90f --- /dev/null +++ b/modules/latestupdates/helpers/latestupdates_block.php @@ -0,0 +1,52 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 latestupdates_block_Core { + static function get_site_list() { + return array("latestupdates" => t("Latest Updates")); + } + + static function get($block_id, $theme) { + $block = ""; + + switch ($block_id) { + case "latestupdates": + + // Make a new sidebar block. + $block = new Block(); + $block->css_id = "g-latest-updates"; + $block->title = t("Latest Updates"); + $block->content = new View("latestupdates_block.html"); + + if (!$theme->item()) { + $block->content->update_links = array( + "Entire Gallery" => url::site("latestupdates/updates")); + } else { + // Determine the ID# of the current album. + $albumID = $theme->item->is_album() ? $theme->item->id : $theme->item->parent_id; + $block->content->update_links = array( + "Entire Gallery" => url::site("latestupdates/updates"), + "This Album" => url::site("latestupdates/albums/$albumID") + ); + } + break; + } + return $block; + } +} diff --git a/modules/latestupdates/helpers/latestupdates_event.php b/modules/latestupdates/helpers/latestupdates_event.php new file mode 100644 index 0000000..3809933 --- /dev/null +++ b/modules/latestupdates/helpers/latestupdates_event.php @@ -0,0 +1,29 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 latestupdates_event_Core { + static function show_user_profile($data) { + // Display links on the user profile pages for recent photos/albums + // and most popular photos from the specified user. + $v = new View("latestupdates_user_profile_info.html"); + $v->user_id = $data->user->id; + $data->content[] = (object) array("title" => t("Latest Updates"), "view" => $v); + } +} diff --git a/modules/latestupdates/helpers/latestupdates_theme.php b/modules/latestupdates/helpers/latestupdates_theme.php new file mode 100644 index 0000000..e6423a8 --- /dev/null +++ b/modules/latestupdates/helpers/latestupdates_theme.php @@ -0,0 +1,27 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 latestupdates_theme_Core { + static function head($theme) { + // Load CSS and JS for jCarouselLite. + return $theme->script("jcarousellite_1.0.1.js") . + $theme->css("latestupdates_jcarousel.css"); + } +} diff --git a/modules/latestupdates/images/imageNavLeft.gif b/modules/latestupdates/images/imageNavLeft.gif Binary files differnew file mode 100644 index 0000000..9c0e5af --- /dev/null +++ b/modules/latestupdates/images/imageNavLeft.gif diff --git a/modules/latestupdates/images/imageNavLeftHover.gif b/modules/latestupdates/images/imageNavLeftHover.gif Binary files differnew file mode 100644 index 0000000..0945b9e --- /dev/null +++ b/modules/latestupdates/images/imageNavLeftHover.gif diff --git a/modules/latestupdates/images/imageNavRight.gif b/modules/latestupdates/images/imageNavRight.gif Binary files differnew file mode 100644 index 0000000..ad4bb00 --- /dev/null +++ b/modules/latestupdates/images/imageNavRight.gif diff --git a/modules/latestupdates/images/imageNavRightHover.gif b/modules/latestupdates/images/imageNavRightHover.gif Binary files differnew file mode 100644 index 0000000..a5abcc9 --- /dev/null +++ b/modules/latestupdates/images/imageNavRightHover.gif diff --git a/modules/latestupdates/js/jcarousellite_1.0.1.js b/modules/latestupdates/js/jcarousellite_1.0.1.js new file mode 100644 index 0000000..07b82a6 --- /dev/null +++ b/modules/latestupdates/js/jcarousellite_1.0.1.js @@ -0,0 +1,341 @@ +/** + * jCarouselLite - jQuery plugin to navigate images/any content in a carousel style widget. + * @requires jQuery v1.2 or above + * + * http://gmarwaha.com/jquery/jcarousellite/ + * + * Copyright (c) 2007 Ganeshji Marwaha (gmarwaha.com) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Version: 1.0.1 + * Note: Requires jquery 1.2 or above from version 1.0.1 + */ + +/** + * Creates a carousel-style navigation widget for images/any-content from a simple HTML markup. + * + * The HTML markup that is used to build the carousel can be as simple as... + * + * <div class="carousel"> + * <ul> + * <li><img src="image/1.jpg" alt="1"></li> + * <li><img src="image/2.jpg" alt="2"></li> + * <li><img src="image/3.jpg" alt="3"></li> + * </ul> + * </div> + * + * As you can see, this snippet is nothing but a simple div containing an unordered list of images. + * You don't need any special "class" attribute, or a special "css" file for this plugin. + * I am using a class attribute just for the sake of explanation here. + * + * To navigate the elements of the carousel, you need some kind of navigation buttons. + * For example, you will need a "previous" button to go backward, and a "next" button to go forward. + * This need not be part of the carousel "div" itself. It can be any element in your page. + * Lets assume that the following elements in your document can be used as next, and prev buttons... + * + * <button class="prev"><<</button> + * <button class="next">>></button> + * + * Now, all you need to do is call the carousel component on the div element that represents it, and pass in the + * navigation buttons as options. + * + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev" + * }); + * + * That's it, you would have now converted your raw div, into a magnificient carousel. + * + * There are quite a few other options that you can use to customize it though. + * Each will be explained with an example below. + * + * @param an options object - You can specify all the options shown below as an options object param. + * + * @option btnPrev, btnNext : string - no defaults + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev" + * }); + * @desc Creates a basic carousel. Clicking "btnPrev" navigates backwards and "btnNext" navigates forward. + * + * @option btnGo - array - no defaults + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev", + * btnGo: [".0", ".1", ".2"] + * }); + * @desc If you don't want next and previous buttons for navigation, instead you prefer custom navigation based on + * the item number within the carousel, you can use this option. Just supply an array of selectors for each element + * in the carousel. The index of the array represents the index of the element. What i mean is, if the + * first element in the array is ".0", it means that when the element represented by ".0" is clicked, the carousel + * will slide to the first element and so on and so forth. This feature is very powerful. For example, i made a tabbed + * interface out of it by making my navigation elements styled like tabs in css. As the carousel is capable of holding + * any content, not just images, you can have a very simple tabbed navigation in minutes without using any other plugin. + * The best part is that, the tab will "slide" based on the provided effect. :-) + * + * @option mouseWheel : boolean - default is false + * @example + * $(".carousel").jCarouselLite({ + * mouseWheel: true + * }); + * @desc The carousel can also be navigated using the mouse wheel interface of a scroll mouse instead of using buttons. + * To get this feature working, you have to do 2 things. First, you have to include the mouse-wheel plugin from brandon. + * Second, you will have to set the option "mouseWheel" to true. That's it, now you will be able to navigate your carousel + * using the mouse wheel. Using buttons and mouseWheel or not mutually exclusive. You can still have buttons for navigation + * as well. They complement each other. To use both together, just supply the options required for both as shown below. + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev", + * mouseWheel: true + * }); + * + * @option auto : number - default is null, meaning autoscroll is disabled by default + * @example + * $(".carousel").jCarouselLite({ + * auto: 800, + * speed: 500 + * }); + * @desc You can make your carousel auto-navigate itself by specfying a millisecond value in this option. + * The value you specify is the amount of time between 2 slides. The default is null, and that disables auto scrolling. + * Specify this value and magically your carousel will start auto scrolling. + * + * @option speed : number - 200 is default + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev", + * speed: 800 + * }); + * @desc Specifying a speed will slow-down or speed-up the sliding speed of your carousel. Try it out with + * different speeds like 800, 600, 1500 etc. Providing 0, will remove the slide effect. + * + * @option easing : string - no easing effects by default. + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev", + * easing: "bounceout" + * }); + * @desc You can specify any easing effect. Note: You need easing plugin for that. Once specified, + * the carousel will slide based on the provided easing effect. + * + * @option vertical : boolean - default is false + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev", + * vertical: true + * }); + * @desc Determines the direction of the carousel. true, means the carousel will display vertically. The next and + * prev buttons will slide the items vertically as well. The default is false, which means that the carousel will + * display horizontally. The next and prev items will slide the items from left-right in this case. + * + * @option circular : boolean - default is true + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev", + * circular: false + * }); + * @desc Setting it to true enables circular navigation. This means, if you click "next" after you reach the last + * element, you will automatically slide to the first element and vice versa. If you set circular to false, then + * if you click on the "next" button after you reach the last element, you will stay in the last element itself + * and similarly for "previous" button and first element. + * + * @option visible : number - default is 3 + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev", + * visible: 4 + * }); + * @desc This specifies the number of items visible at all times within the carousel. The default is 3. + * You are even free to experiment with real numbers. Eg: "3.5" will have 3 items fully visible and the + * last item half visible. This gives you the effect of showing the user that there are more images to the right. + * + * @option start : number - default is 0 + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev", + * start: 2 + * }); + * @desc You can specify from which item the carousel should start. Remember, the first item in the carousel + * has a start of 0, and so on. + * + * @option scrool : number - default is 1 + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev", + * scroll: 2 + * }); + * @desc The number of items that should scroll/slide when you click the next/prev navigation buttons. By + * default, only one item is scrolled, but you may set it to any number. Eg: setting it to "2" will scroll + * 2 items when you click the next or previous buttons. + * + * @option beforeStart, afterEnd : function - callbacks + * @example + * $(".carousel").jCarouselLite({ + * btnNext: ".next", + * btnPrev: ".prev", + * beforeStart: function(a) { + * alert("Before animation starts:" + a); + * }, + * afterEnd: function(a) { + * alert("After animation ends:" + a); + * } + * }); + * @desc If you wanted to do some logic in your page before the slide starts and after the slide ends, you can + * register these 2 callbacks. The functions will be passed an argument that represents an array of elements that + * are visible at the time of callback. + * + * + * @cat Plugins/Image Gallery + * @author Ganeshji Marwaha/ganeshread@gmail.com + */ + +(function($) { // Compliant with jquery.noConflict() +$.fn.jCarouselLite = function(o) { + o = $.extend({ + btnPrev: null, + btnNext: null, + btnGo: null, + mouseWheel: false, + auto: null, + + speed: 200, + easing: null, + + vertical: false, + circular: true, + visible: 3, + start: 0, + scroll: 1, + + beforeStart: null, + afterEnd: null + }, o || {}); + + return this.each(function() { // Returns the element collection. Chainable. + + var running = false, animCss=o.vertical?"top":"left", sizeCss=o.vertical?"height":"width"; + var div = $(this), ul = $("ul", div), tLi = $("li", ul), tl = tLi.size(), v = o.visible; + + if(o.circular) { + ul.prepend(tLi.slice(tl-v-1+1).clone()) + .append(tLi.slice(0,v).clone()); + o.start += v; + } + + var li = $("li", ul), itemLength = li.size(), curr = o.start; + div.css("visibility", "visible"); + + li.css({overflow: "hidden", float: o.vertical ? "none" : "left"}); + ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"}); + div.css({overflow: "hidden", position: "relative", "z-index": "2", left: "0px"}); + + var liSize = o.vertical ? height(li) : width(li); // Full li size(incl margin)-Used for animation + var ulSize = liSize * itemLength; // size of full ul(total length, not just for the visible items) + var divSize = liSize * v; // size of entire div(total length for just the visible items) + + li.css({width: li.width(), height: li.height()}); + ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize)); + + div.css(sizeCss, divSize+"px"); // Width of the DIV. length of visible images + + if(o.btnPrev) + $(o.btnPrev).click(function() { + return go(curr-o.scroll); + }); + + if(o.btnNext) + $(o.btnNext).click(function() { + return go(curr+o.scroll); + }); + + if(o.btnGo) + $.each(o.btnGo, function(i, val) { + $(val).click(function() { + return go(o.circular ? o.visible+i : i); + }); + }); + + if(o.mouseWheel && div.mousewheel) + div.mousewheel(function(e, d) { + return d>0 ? go(curr-o.scroll) : go(curr+o.scroll); + }); + + if(o.auto) + setInterval(function() { + go(curr+o.scroll); + }, o.auto+o.speed); + + function vis() { + return li.slice(curr).slice(0,v); + }; + + function go(to) { + if(!running) { + + if(o.beforeStart) + o.beforeStart.call(this, vis()); + + if(o.circular) { // If circular we are in first or last, then goto the other end + if(to<=o.start-v-1) { // If first, then goto last + ul.css(animCss, -((itemLength-(v*2))*liSize)+"px"); + // If "scroll" > 1, then the "to" might not be equal to the condition; it can be lesser depending on the number of elements. + curr = to==o.start-v-1 ? itemLength-(v*2)-1 : itemLength-(v*2)-o.scroll; + } else if(to>=itemLength-v+1) { // If last, then goto first + ul.css(animCss, -( (v) * liSize ) + "px" ); + // If "scroll" > 1, then the "to" might not be equal to the condition; it can be greater depending on the number of elements. + curr = to==itemLength-v+1 ? v+1 : v+o.scroll; + } else curr = to; + } else { // If non-circular and to points to first or last, we just return. + if(to<0 || to>itemLength-v) return; + else curr = to; + } // If neither overrides it, the curr will still be "to" and we can proceed. + + running = true; + + ul.animate( + animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing, + function() { + if(o.afterEnd) + o.afterEnd.call(this, vis()); + running = false; + } + ); + // Disable buttons when the carousel reaches the last/first, and enable when not + if(!o.circular) { + $(o.btnPrev + "," + o.btnNext).removeClass("disabled"); + $( (curr-o.scroll<0 && o.btnPrev) + || + (curr+o.scroll > itemLength-v && o.btnNext) + || + [] + ).addClass("disabled"); + } + + } + return false; + }; + }); +}; + +function css(el, prop) { + return parseInt($.css(el[0], prop)) || 0; +}; +function width(el) { + return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight'); +}; +function height(el) { + return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom'); +}; + +})(jQuery);
\ No newline at end of file diff --git a/modules/latestupdates/module.info b/modules/latestupdates/module.info new file mode 100644 index 0000000..95aba92 --- /dev/null +++ b/modules/latestupdates/module.info @@ -0,0 +1,7 @@ +name = "LatestUpdates" +description = "Display recently uploaded photos and videos." +version = 1 +author_name = "rWatcher" +author_url = "http://codex.gallery2.org/User:RWatcher" +info_url = "http://codex.gallery2.org/Gallery3:Modules:latestupdates" +discuss_url = "http://gallery.menalto.com/node/88936" diff --git a/modules/latestupdates/views/latestupdates_block.html.php b/modules/latestupdates/views/latestupdates_block.html.php new file mode 100644 index 0000000..b1dd907 --- /dev/null +++ b/modules/latestupdates/views/latestupdates_block.html.php @@ -0,0 +1,10 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<ul id="g-update-list"> +<? foreach($update_links as $title => $url): ?> + <li style="clear: both;"> + <a href="<?= $url ?>"> + <?= t($title) ?> + </a> + </li> +<? endforeach ?> +</ul> diff --git a/modules/latestupdates/views/latestupdates_user_profile_carousel.html.php b/modules/latestupdates/views/latestupdates_user_profile_carousel.html.php new file mode 100644 index 0000000..1a3ada2 --- /dev/null +++ b/modules/latestupdates/views/latestupdates_user_profile_carousel.html.php @@ -0,0 +1,37 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<? if (count($items) == 0): ?> + <center><?=t("This user hasn't uploaded anything yet."); ?></center> +<? else: ?> +<script> +$(document).ready(function() { setTimeout(LoadCarousel, 100); }); +function LoadCarousel() { + $(".main .jCarouselLite").jCarouselLite({ + btnNext: ".next", + btnPrev: ".prev", + visible: 4, + circular: false + }); +} +</script> +<div id="jCarouselLite" class="cEnd" style="width: 570px;"> + <div class="carousel main"> + <a href="#" class="prev"> </a> + <div class="jCarouselLite"> + <ul> + <? foreach ($items as $photo): ?> + <li class="g-item g-photo"> + <a href="<?= $photo->url() ?>" title="<?= html::purify($photo->title)->for_html_attr() ?>"> + <img <?= photo::img_dimensions($photo->thumb_width, $photo->thumb_height, 100) ?> + src="<?= $photo->thumb_url() ?>" alt="<?= html::purify($photo->title)->for_html_attr() ?>" /> + </a> + </li> + <? endforeach ?> + </ul> + </div> + <a href="#" class="next"> </a> + <div class="clear"></div> + </div> +</div> +<br /> +<div style="width: 510px; text-align: right;"><a href="<?=$str_view_more_url; ?>"><?=$str_view_more_title; ?> >></a></div> +<? endif; ?> diff --git a/modules/latestupdates/views/latestupdates_user_profile_info.html.php b/modules/latestupdates/views/latestupdates_user_profile_info.html.php new file mode 100644 index 0000000..175d137 --- /dev/null +++ b/modules/latestupdates/views/latestupdates_user_profile_info.html.php @@ -0,0 +1,14 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<script> + $(function() { + $( "#tabs" ).tabs(); + }); +</script> +<br /> +<div id="tabs" style="height: 200px"> + <ul> + <li><a class="g-menu-link" href="<?= url::site("latestupdates/user_profiles/popular/{$user_id}") ?>" title="<?= t("Most Viewed") ?>"><?= t("Most Viewed") ?></a></li> + <li><a class="g-menu-link" href="<?= url::site("latestupdates/user_profiles/recent/{$user_id}") ?>" title="<?= t("Recent Uploads") ?>"><?= t("Recent Uploads") ?></a></li> + <li><a class="g-menu-link" href="<?= url::site("latestupdates/user_profiles/albums/{$user_id}") ?>" title="<?= t("Recent Albums") ?>"><?= t("Recent Albums") ?></a></li> + </ul> +</div> diff --git a/modules/tag_cloud_html5/controllers/admin_tag_cloud_html5.php b/modules/tag_cloud_html5/controllers/admin_tag_cloud_html5.php new file mode 100644 index 0000000..98ffe6e --- /dev/null +++ b/modules/tag_cloud_html5/controllers/admin_tag_cloud_html5.php @@ -0,0 +1,252 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 Admin_Tag_Cloud_Html5_Controller extends Admin_Controller { + public function index() { + // print screen from new form + $form = $this->_get_admin_form(); + $this->_print_screen($form); + } + + public function edit() { + access::verify_csrf(); + $cfg = $this->_get_config(); + $form = $this->_get_admin_form(); + if ($form->validate()) { + if ($form->general->reset_defaults->value) { + // reset all to defaults, redirect with message + module::install("tag_cloud_html5"); + message::success(t("Tag cloud options reset successfully")); + url::redirect("admin/tag_cloud_html5"); + } + // save the new inputs + module::set_var("tag_cloud_html5", "show_wholecloud_link", ($form->general->show_wholecloud_link->value == 1)); + module::set_var("tag_cloud_html5", "show_add_tag_form", ($form->general->show_add_tag_form->value == 1)); + module::set_var("tag_cloud_html5", "show_wholecloud_list", ($form->general->show_wholecloud_list->value == 1)); + foreach ($cfg['groups'] as $groupname => $grouptext) { + module::set_var("tag_cloud_html5", "maxtags".$groupname, $form->{"size".$groupname}->{"maxtags".$groupname}->value); + module::set_var("tag_cloud_html5", "width".$groupname, $form->{"size".$groupname}->{"width".$groupname}->value); + module::set_var("tag_cloud_html5", "height".$groupname, $form->{"size".$groupname}->{"height".$groupname}->value); + + $optionsarray = array(); + // group size + $optionsarray['shape'] = $form->{"size".$groupname}->{"shape".$groupname}->value; + $optionsarray['zoom'] = $form->{"size".$groupname}->{"zoom".$groupname}->value; + $optionsarray['stretchX'] = $form->{"size".$groupname}->{"stretchX".$groupname}->value; + $optionsarray['stretchY'] = $form->{"size".$groupname}->{"stretchY".$groupname}->value; + // group motion + $optionsarray['maxSpeed'] = $form->{"motion".$groupname}->{"maxSpeed".$groupname}->value; + $optionsarray['minSpeed'] = $form->{"motion".$groupname}->{"minSpeed".$groupname}->value; + $optionsarray['deadZone'] = $form->{"motion".$groupname}->{"deadZone".$groupname}->value; + $optionsarray['decel'] = $form->{"motion".$groupname}->{"decel".$groupname}->value; + $optionsarray['initial'] = array($form->{"motion".$groupname}->{"initialX".$groupname}->value, $form->{"motion".$groupname}->{"initialY".$groupname}->value); + $optionsarray['maxInputZone'] = $form->{"motion".$groupname}->{"maxInputZone".$groupname}->value; + // group select + $optionsarray['outlineMethod'] = $form->{"select".$groupname}->{"outlineMethod".$groupname}->value; + $optionsarray['outlineOffset'] = $form->{"select".$groupname}->{"outlineOffset".$groupname}->value; + $optionsarray['outlineColour'] = $form->{"select".$groupname}->{"outlineColour".$groupname}->value; + $optionsarray['frontSelect'] = ($form->{"select".$groupname}->{"frontSelect".$groupname}->value == 1); + // group appearance + $optionsarray['textHeight'] = $form->{"appearance".$groupname}->{"textHeight".$groupname}->value; + $optionsarray['textColour'] = $form->{"appearance".$groupname}->{"textColour".$groupname}->value; + $optionsarray['textFont'] = $form->{"appearance".$groupname}->{"textFont".$groupname}->value; + $optionsarray['depth'] = $form->{"appearance".$groupname}->{"depth".$groupname}->value; + // options that are not explicitly defined in admin menu + $optionsarray['wheelZoom'] = false; // otherwise scrolling through the page screws everything up (was a problem in v1) + $optionsarray['initialDecel'] = true; // this was an option in v4, but it's sorta useless - use minSpeed for a related but better effect + $optionsarray['physModel'] = true; // this is the big enhancement for v5, and is a major modification that I did to TagCanvas + switch ($optionsarray['shape']) { + case "hcylinder": + // keep it horizontal - lock x-axis rotation + $optionsarray['lock'] = "x"; + break; + case "vcylinder": + // keep it vertical - lock y-axis rotation + $optionsarray['lock'] = "y"; + break; + default: + // do not lock either axis + $optionsarray['lock'] = ""; + } + module::set_var("tag_cloud_html5", "options".$groupname, json_encode($optionsarray)); + } + // all done; redirect with message + message::success(t("Tag cloud options updated successfully")); + url::redirect("admin/tag_cloud_html5"); + } + // not valid - print screen from existing form + $this->_print_screen($form); + } + + private function _get_config() { + // these define the two variable name groups, along with their labels which are always shown with t() for i18n. + $cfg['groups'] = array("_sidebar"=>t("Sidebar"), "_wholecloud"=>t("Whole cloud")); + // this defines the separator that's used between the group name and the attribute, and is *not* put through t(). + $cfg['sep'] = " : "; + // this is used in the labels of the width/height parameters + $cfg['size'] = array("_sidebar"=>t("as fraction of sidebar width, e.g. 'g-block-content' class"), "_wholecloud"=>t("as fraction of browser window height")); + return $cfg; + } + + private function _print_screen($form) { + // this part is a bit of a hack, but Forge doesn't seem to allow set_attr() for groups. + $form = $form->render(); + $form = preg_replace("/<fieldset>/","<fieldset class=\"g-tag-cloud-html5-admin-form-top\">",$form,1); + $form = preg_replace("/<fieldset>/","<fieldset class=\"g-tag-cloud-html5-admin-form-left\">",$form,4); + $form = preg_replace("/<fieldset>/","<fieldset class=\"g-tag-cloud-html5-admin-form-right\">",$form,4); + + $view = new Admin_View("admin.html"); + $view->content = new View("admin_tag_cloud_html5.html"); + $view->content->form = $form; + print $view; + } + + private function _get_admin_form() { + $cfg = $this->_get_config(); + $sep = $cfg['sep']; + + // Make the main form. This form has *nine* groups: general, then size, motion, select, and appearance for _sidebar and _wholecloud. + $form = new Forge("admin/tag_cloud_html5/edit", "", "post", array("id" => "g-tag-cloud-html5-admin-form")); + + // group general + $group_general = $form->group("general")->label(t("General"))->set_attr("id","g-tag-cloud-html5-admin-form-general"); + $group_general->checkbox("reset_defaults") + ->label(t("Reset all to default values")) + ->checked(false); + $group_general->checkbox("show_wholecloud_link") + ->label(t("Show 'View whole cloud' link in sidebar")) + ->checked(module::get_var("tag_cloud_html5", "show_wholecloud_link", null)); + $group_general->checkbox("show_add_tag_form") + ->label(t("Show 'Add tag to album' form in sidebar (when permitted and applicable)")) + ->checked(module::get_var("tag_cloud_html5", "show_add_tag_form", null)); + $group_general->checkbox("show_wholecloud_list") + ->label(t("Show inline tag list under cloud on 'View whole cloud' page")." {hideTags}") + ->checked(module::get_var("tag_cloud_html5", "show_wholecloud_list", null)); + + foreach ($cfg['groups'] as $groupname => $grouptext) { + $maxtags = strval(module::get_var("tag_cloud_html5", "maxtags".$groupname, null)); + $width = strval(module::get_var("tag_cloud_html5", "width".$groupname, null)); + $height = strval(module::get_var("tag_cloud_html5", "height".$groupname, null)); + $options = json_decode(module::get_var("tag_cloud_html5", "options".$groupname, null),true); + + // group size/shape + ${"group_size".$groupname} = $form->group("size".$groupname)->label(t("Size and shape").$sep.$grouptext); + ${"group_size".$groupname}->input("maxtags".$groupname) + ->label(t("maximum tags shown")) + ->value($maxtags) + ->rules("required|numrange[0]"); + ${"group_size".$groupname}->input("width".$groupname) + ->label(t("width")." (".$cfg['size'][$groupname].")") + ->value($width) + ->rules("required|numrange[0]"); + ${"group_size".$groupname}->input("height".$groupname) + ->label(t("height")." (".$cfg['size'][$groupname].")") + ->value($height) + ->rules("required|numrange[0]"); + ${"group_size".$groupname}->dropdown("shape".$groupname) + ->label(t("shape of cloud")." {shape,lock}") + ->options(array("sphere"=>t("sphere"),"hcylinder"=>t("horizontal cylinder"),"vcylinder"=>t("vertical cylinder"))) + ->selected($options['shape']); + ${"group_size".$groupname}->input("zoom".$groupname) + ->label(t("zoom (<1.0 is zoom out, >1.0 is zoom in)")." {zoom}") + ->value($options['zoom']) + ->rules("required|numrange[0]"); + ${"group_size".$groupname}->input("stretchX".$groupname) + ->label(t("x-axis stretch factor (<1.0 squishes, >1.0 stretches)")." {stretchX}") + ->value($options['stretchX']) + ->rules("required|numrange[0]"); + ${"group_size".$groupname}->input("stretchY".$groupname) + ->label(t("y-axis stretch factor (<1.0 squishes, >1.0 stretches)")." {stretchY}") + ->value($options['stretchY']) + ->rules("required|numrange[0]"); + + // group motion + ${"group_motion".$groupname} = $form->group("motion".$groupname)->label(t("Motion").$sep.$grouptext); + ${"group_motion".$groupname}->input("maxSpeed".$groupname) + ->label(t("max speed (typically 0.01-0.20)")." {maxSpeed}") + ->value($options['maxSpeed']) + ->rules("required|numrange[0]"); + ${"group_motion".$groupname}->input("minSpeed".$groupname) + ->label(t("no mouseover speed (typically 0.00-0.01)")." {minSpeed}") + ->value($options['minSpeed']) + ->rules("required|numrange[0]"); + ${"group_motion".$groupname}->input("deadZone".$groupname) + ->label(t("dead zone size (0.0-1.0 - 0.0 is none and 1.0 is entire cloud)")." {deadZone}") + ->value($options['deadZone']) + ->rules("required|numrange[0,1]"); + ${"group_motion".$groupname}->input("decel".$groupname) + ->label(t("inertia (0.0-1.0 - 0.0 changes velocity instantly and 1.0 never changes)")." {decel}") + ->value($options['decel']) + ->rules("required|numrange[0,1]"); + ${"group_motion".$groupname}->input("initialX".$groupname) + ->label(t("initial horizontal speed (between +/-1.0, as fraction of max speed)")." {initial}") + ->value($options['initial'][0]) + ->rules("required|numrange[-1,1]"); + ${"group_motion".$groupname}->input("initialY".$groupname) + ->label(t("initial vertical speed (between +/-1.0, as fraction of max speed)")." {initial}") + ->value($options['initial'][1]) + ->rules("required|numrange[-1,1]"); + ${"group_motion".$groupname}->input("maxInputZone".$groupname) + ->label(t("mouseover region beyond cloud (as fraction of cloud - 0.0 is tight around cloud)")." {maxInputZone}") + ->value($options['maxInputZone']) + ->rules("required|numrange[0]"); + + // group select + ${"group_select".$groupname} = $form->group("select".$groupname)->label(t("Tag selection").$sep.$grouptext); + ${"group_select".$groupname}->dropdown("outlineMethod".$groupname) + ->label(t("change of display for selected tag")." {outlineMethod}") + ->options(array("colour"=>t("change text color"),"outline"=>t("add outline around text"),"block"=>t("add block behind text"))) + ->selected($options['outlineMethod']); + ${"group_select".$groupname}->input("outlineOffset".$groupname) + ->label(t("mouseover region around tag text (in pixels - 0 is tight around text)")." {outlineOffset}") + ->value($options['outlineOffset']) + ->rules("required|numrange[0]"); + ${"group_select".$groupname}->input("outlineColour".$groupname) + ->label(t("color used for change of display (as #hhhhhh)")." {outlineColour}") + ->value($options['outlineColour']) + ->rules('required|color'); + ${"group_select".$groupname}->checkbox("frontSelect".$groupname) + ->label(t("only allow tags in front to be selected")." {frontSelect}") + ->checked($options['frontSelect']); + + // group appearance + ${"group_appearance".$groupname} = $form->group("appearance".$groupname)->label(t("Appearance").$sep.$grouptext); + ${"group_appearance".$groupname}->input("textHeight".$groupname) + ->label(t("text height (in pixels)")." {textHeight}") + ->value($options['textHeight']) + ->rules("required|numrange[0]"); + ${"group_appearance".$groupname}->input("textColour".$groupname) + ->label(t("text color (as #hhhhhh, or empty to use theme color)")." {textColour}") + ->value($options['textColour']) + ->rules('color'); + ${"group_appearance".$groupname}->input("textFont".$groupname) + ->label(t("text font family (empty to use theme font family)")." {textFont}") + ->value($options['textFont']) + ->rules("length[0,60]"); + ${"group_appearance".$groupname}->input("depth".$groupname) + ->label(t("depth/perspective of cloud (0.0-1.0 - 0.0 is none and >0.9 gets strange)")." {depth}") + ->value($options['depth']) + ->rules("required|numrange[0,1]"); + } + $form->submit("")->value(t("Save")); + + return $form; + } + +} diff --git a/modules/tag_cloud_html5/controllers/tag_cloud.php b/modules/tag_cloud_html5/controllers/tag_cloud.php new file mode 100644 index 0000000..e12216b --- /dev/null +++ b/modules/tag_cloud_html5/controllers/tag_cloud.php @@ -0,0 +1,114 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 Tag_Cloud_Controller extends Controller { + public function index() { + // Require view permission for the root album for security purposes. + $album = ORM::factory("item", 1); + access::required("view", $album); + + // Get settings + $options = module::get_var("tag_cloud_html5", "options_wholecloud", null); + $maxtags = module::get_var("tag_cloud_html5", "maxtags_wholecloud", null); + $width = module::get_var("tag_cloud_html5", "width_wholecloud", null); + $height = module::get_var("tag_cloud_html5", "height_wholecloud", null); + + $options = json_decode($options, true); + $options['hideTags'] = !module::get_var("tag_cloud_html5", "show_wholecloud_list", true); + $options = json_encode($options); + + // Set up and display the actual page. + $template = new Theme_View("page.html", "other", "Tag cloud"); + $template->content = new View("tag_cloud_html5_page.html"); + $template->content->title = t("Tag cloud"); + $template->content->cloud = tag::cloud($maxtags); + $template->content->options = $options; + $template->content->width = $width; + $template->content->height = $height; + + // Display the page. + print $template; + } + + public function embed() { + /** + * This is used to embed the tag cloud in other things. New in version 7. + * + * It expects the url to be in the form: + * tag_cloud/embed/optionsbase/option1/value1/option2/value2/.../optionN/valueN + * Where: + * optionsbase = "sidebar" or "wholecloud" (takes settings from this config) + * optionX = option name (either "maxtags" or any of the TagCanvas parameters - no name verification performed!) + * valueX = value of option (no value verification performed here!) + * Here's how the tag cloud is built: + * 1. Load "maxtags" and "options" variables for optionbase (as defined in admin menu or admin/advanced variables) + * Note: width and height are ignored, and the add tag form, wholecloud link, and inline tags are not shown. + * 2. Use option/value pairs to override and/or append those loaded above. + * 3. Build tag cloud, using 100% of the size from its parent. + * Correspondingly, the optionsbase is required, but the options and values are not. + */ + + // Require view permission for the root album for security purposes. + $album = ORM::factory("item", 1); + access::required("view", $album); + + // get the function arguments + $args = func_get_args(); + + // get/check the number of arguments - must be odd + $countargs = count($args); + if ($countargs % 2 == 0) { + return; + } + + // get/check the first argument - must be sidebar or wholecloud + $optionsbase = $args[0]; + if (!(in_array($optionsbase, array("sidebar", "wholecloud")))) { + return; + } + + // get and override/append options/values + $maxtags = module::get_var("tag_cloud_html5", "maxtags_".$optionsbase, null); + $options = module::get_var("tag_cloud_html5", "options_".$optionsbase, null); + $options = json_decode($options, true); + for ($i = 0; $i < ($countargs-1)/2; $i++) { + $option = $args[2*$i+1]; + $value = $args[2*$i+2]; + if ($option == "maxtags") { + // assign to maxtags + $maxtags = $value; + } elseif (substr($option,-6) == 'Colour') { + // assign to options with a hash in front + $options[$option] = '#'.$value; + } else { + // assign to options + $options[$option] = $value; + } + } + $options = json_encode($options); + + // Set up and display the actual page. + $template = new View("tag_cloud_html5_embed.html"); + $template->cloud = tag::cloud($maxtags); + $template->options = $options; + + // Display the page. + print $template; + } +}
\ No newline at end of file diff --git a/modules/tag_cloud_html5/css/admin_tag_cloud_html5.css b/modules/tag_cloud_html5/css/admin_tag_cloud_html5.css new file mode 100644 index 0000000..e87717f --- /dev/null +++ b/modules/tag_cloud_html5/css/admin_tag_cloud_html5.css @@ -0,0 +1,17 @@ +#g-content fieldset {
+ display: block;
+}
+#g-content fieldset.g-tag-cloud-html5-admin-form-top {
+ width: 80%;
+ clear: both;
+}
+#g-content fieldset.g-tag-cloud-html5-admin-form-left {
+ width: 45%;
+ float: left;
+ clear: left;
+ margin-right: 2%
+}
+#g-content fieldset.g-tag-cloud-html5-admin-form-right {
+ width: 45%;
+ clear: right;
+}
\ No newline at end of file diff --git a/modules/tag_cloud_html5/css/tag_cloud_html5.css b/modules/tag_cloud_html5/css/tag_cloud_html5.css new file mode 100644 index 0000000..bf20ce8 --- /dev/null +++ b/modules/tag_cloud_html5/css/tag_cloud_html5.css @@ -0,0 +1,106 @@ +/* Tag cloud - sidebar ~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* comment out this first block to make the inline tags appear if the cloud doesn't load */
+#g-tag-cloud-html5-tags {
+ display: none;
+}
+
+#g-tag-cloud-html5-tags ul {
+ text-align: justify;
+}
+
+#g-tag-cloud-html5-tags ul li {
+ display: inline;
+ text-align: justify;
+}
+
+#g-tag-cloud-html5-tags ul li a {
+ text-decoration: none;
+}
+
+#g-tag-cloud-html5-tags ul li span {
+ display: none;
+}
+
+#g-tag-cloud-html5-page ul li a:hover {
+ text-decoration: underline;
+}
+
+/* Tag cloud - whole cloud page ~~~~~~~~~~~~~~~~~~~~~~~ */
+
+#g-tag-cloud-html5-page-canvas {
+ display: block;
+ margin: 0 auto;
+}
+
+#g-tag-cloud-html5-page-tags ul {
+ font-size: 1.2em;
+ text-align: justify;
+}
+
+#g-tag-cloud-html5-page-tags ul li {
+ display: inline;
+ line-height: 1.5em;
+ text-align: justify;
+}
+
+#g-tag-cloud-html5-page-tags ul li a {
+ text-decoration: none;
+}
+
+#g-tag-cloud-html5-page-tags ul li span {
+ display: none;
+}
+
+#g-tag-cloud-html5-page-tags ul li.size0 a {
+ color: #9cf;
+ font-size: 70%;
+ font-weight: 100;
+}
+
+#g-tag-cloud-html5-page-tags ul li.size1 a {
+ color: #9cf;
+ font-size: 80%;
+ font-weight: 100;
+}
+
+#g-tag-cloud-html5-page-tags ul li.size2 a {
+ color: #69f;
+ font-size: 90%;
+ font-weight: 300;
+}
+
+#g-tag-cloud-html5-page-tags ul li.size3 a {
+ color: #69c;
+ font-size: 100%;
+ font-weight: 500;
+}
+
+#g-tag-cloud-html5-page-tags ul li.size4 a {
+ color: #369;
+ font-size: 110%;
+ font-weight: 700;
+}
+
+#g-tag-cloud-html5-page-tags ul li.size5 a {
+ color: #0e2b52;
+ font-size: 120%;
+ font-weight: 900;
+}
+
+#g-tag-cloud-html5-page-tags ul li.size6 a {
+ color: #0e2b52;
+ font-size: 130%;
+ font-weight: 900;
+}
+
+#g-tag-cloud-html5-page-tags ul li.size7 a {
+ color: #0e2b52;
+ font-size: 140%;
+ font-weight: 900;
+}
+
+#g-tag-cloud-html5-page-tags ul li a:hover {
+ color: #f30;
+ text-decoration: underline;
+}
diff --git a/modules/tag_cloud_html5/helpers/tag_cloud_html5_block.php b/modules/tag_cloud_html5/helpers/tag_cloud_html5_block.php new file mode 100644 index 0000000..bc99af7 --- /dev/null +++ b/modules/tag_cloud_html5/helpers/tag_cloud_html5_block.php @@ -0,0 +1,67 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 tag_cloud_html5_block { + static function get_site_list() { + return array( + "tag_cloud_html5_site" => (t("Tag cloud")." HTML5")); + } + + static function get($block_id, $theme) { + $block = ""; + switch ($block_id) { + case "tag_cloud_html5_site": + // load settings + $options = module::get_var("tag_cloud_html5", "options_sidebar", null); + $maxtags = module::get_var("tag_cloud_html5", "maxtags_sidebar", null); + $showlink = module::get_var("tag_cloud_html5", "show_wholecloud_link", null); + $showaddtag = module::get_var("tag_cloud_html5", "show_add_tag_form", null); + $width = module::get_var("tag_cloud_html5", "width_sidebar", null); + $height = module::get_var("tag_cloud_html5", "height_sidebar", null); + + // make the block + $block = new Block(); + $block->css_id = "g-tag"; + $block->title = t("Tag cloud"); + $block->content = new View("tag_cloud_html5_block.html"); + $block->content->cloud = tag::cloud($maxtags); + $block->content->options = $options; + $block->content->width = $width; + $block->content->height = $height; + + // add the 'View whole cloud' link if needed + if ($showlink) { + $block->content->wholecloud_link = "<a href=".url::site("tag_cloud/").">".t("View whole cloud")."</a>"; + } else { + $block->content->wholecloud_link = ""; + } + + // add the 'Add tag' form if needed + if ($theme->item() && $theme->page_subtype() != "tag" && access::can("edit", $theme->item()) && $showaddtag) { + $controller = new Tags_Controller(); + $block->content->form = tag::get_add_form($theme->item()); + } else { + $block->content->form = ""; + } + + break; + } + return $block; + } +} diff --git a/modules/tag_cloud_html5/helpers/tag_cloud_html5_event.php b/modules/tag_cloud_html5/helpers/tag_cloud_html5_event.php new file mode 100644 index 0000000..85443a2 --- /dev/null +++ b/modules/tag_cloud_html5/helpers/tag_cloud_html5_event.php @@ -0,0 +1,28 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 tag_cloud_html5_event_Core { + static function admin_menu($menu, $theme) { + $menu->get("settings_menu") + ->append(Menu::factory("link") + ->id("tag_cloud_html5") + ->label(t("Tag cloud")." HTML5") + ->url(url::site("admin/tag_cloud_html5"))); + } +} diff --git a/modules/tag_cloud_html5/helpers/tag_cloud_html5_installer.php b/modules/tag_cloud_html5/helpers/tag_cloud_html5_installer.php new file mode 100644 index 0000000..4b2914d --- /dev/null +++ b/modules/tag_cloud_html5/helpers/tag_cloud_html5_installer.php @@ -0,0 +1,177 @@ +<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 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 tag_cloud_html5_installer {
+ static function install() {
+ // clear and reset default values. this is also called in the admin menu for
+ // 'reset all to default values' and if the upgrader sees variables missing.
+ module::clear_all_vars("tag_cloud_html5");
+
+ module::set_var("tag_cloud_html5", "show_wholecloud_link", true);
+ module::set_var("tag_cloud_html5", "show_add_tag_form", true);
+ module::set_var("tag_cloud_html5", "show_wholecloud_list", true);
+
+ module::set_var("tag_cloud_html5", "maxtags_sidebar", 30);
+ module::set_var("tag_cloud_html5", "width_sidebar", 1.00);
+ module::set_var("tag_cloud_html5", "height_sidebar", 0.8);
+
+ module::set_var("tag_cloud_html5", "maxtags_wholecloud", 500);
+ module::set_var("tag_cloud_html5", "width_wholecloud", 0.95);
+ module::set_var("tag_cloud_html5", "height_wholecloud", 0.75);
+
+ module::set_var("tag_cloud_html5", "options_sidebar", json_encode(array(
+ "maxSpeed" => 0.05,
+ "deadZone" => 0.25,
+ "initial" => array(0.8,-0.3),
+ "initialDecel" => true,
+ "zoom" => 1.25,
+ "depth" => 0.5,
+ "outlineMethod" => "colour",
+ "outlineOffset" => 8,
+ "outlineColour" => "#eeeeee",
+ "textColour" => "",
+ "textFont" => "",
+ "textHeight" => 12,
+ "frontSelect" => true,
+ "wheelZoom" => false,
+ "shape" => "sphere",
+ "lock" => "",
+ "stretchX" => 1.0,
+ "stretchY" => 1.0,
+ "decel" => 0.92,
+ "physModel" => true,
+ "maxInputZone" => 0.25,
+ "minSpeed" => 0.002
+ )));
+
+ module::set_var("tag_cloud_html5", "options_wholecloud", json_encode(array(
+ "maxSpeed" => 0.05,
+ "deadZone" => 0.25,
+ "initial" => array(0.8,-0.3),
+ "initialDecel" => true,
+ "zoom" => 1.25,
+ "depth" => 0.5,
+ "outlineMethod" => "colour",
+ "outlineOffset" => 8,
+ "outlineColour" => "#eeeeee",
+ "textColour" => "",
+ "textFont" => "",
+ "textHeight" => 13,
+ "frontSelect" => true,
+ "wheelZoom" => false,
+ "shape" => "sphere",
+ "lock" => "",
+ "stretchX" => 1.0,
+ "stretchY" => 1.0,
+ "decel" => 0.92,
+ "physModel" => true,
+ "maxInputZone" => 0.15,
+ "minSpeed" => 0.002
+ )));
+
+ module::set_version("tag_cloud_html5", 7);
+ }
+
+ static function upgrade() {
+ if (is_null(module::get_var("tag_cloud_html5", "options_sidebar")) ||
+ is_null(module::get_var("tag_cloud_html5", "options_wholecloud")) ||
+ (module::get_version("tag_cloud_html5") < 1) ) {
+
+ module::install("tag_cloud_html5");
+ }
+ if (module::get_version("tag_cloud_html5") < 2) {
+ // added wheelZoom, which is not accessible from admin menu
+ $options = json_decode(module::get_var("tag_cloud_html5", "options_sidebar"),true);
+ $options["wheelZoom"] = false;
+ module::set_var("tag_cloud_html5", "options_sidebar", json_encode($options));
+
+ $options = json_decode(module::get_var("tag_cloud_html5", "options_wholecloud"),true);
+ $options["wheelZoom"] = false;
+ module::set_var("tag_cloud_html5", "options_wholecloud", json_encode($options));
+ }
+ if (module::get_version("tag_cloud_html5") < 3) {
+ // added deadZone, initial, and initialDecel
+
+ module::set_var("tag_cloud_html5", "show_add_tag_form", true);
+
+ $options = json_decode(module::get_var("tag_cloud_html5", "options_sidebar"),true);
+ $options["deadZone"] = 0.25;
+ $options["initial"] = array(0.8,-0.3);
+ $options["initialDecel"] = true;
+ module::set_var("tag_cloud_html5", "options_sidebar", json_encode($options));
+
+ $options = json_decode(module::get_var("tag_cloud_html5", "options_wholecloud"),true);
+ $options["deadZone"] = 0.25;
+ $options["initial"] = array(0.8,-0.3);
+ $options["initialDecel"] = true;
+ module::set_var("tag_cloud_html5", "options_wholecloud", json_encode($options));
+ }
+ if (module::get_version("tag_cloud_html5") < 4) {
+ // added height_sidebar, then scaled back zoom and textHeight for consistency
+ module::set_var("tag_cloud_html5", "height_sidebar", 0.8);
+
+ $options = json_decode(module::get_var("tag_cloud_html5", "options_sidebar"),true);
+ $options["zoom"] = $options["zoom"] / 0.8;
+ $options["textHeight"] = $options["textHeight"] * 0.8;
+ module::set_var("tag_cloud_html5", "options_sidebar", json_encode($options));
+ }
+ if (module::get_version("tag_cloud_html5") < 5) {
+ // added lots of options that are on admin menu
+ // added physModel, lock, and initialDecel as options not on admin menu
+ // (previously initialDecel was on menu, so reset here)
+
+ module::set_var("tag_cloud_html5", "width_sidebar", 1.00);
+ module::set_var("tag_cloud_html5", "width_wholecloud", 0.95);
+ module::set_var("tag_cloud_html5", "height_wholecloud", 0.75);
+
+ $options = json_decode(module::get_var("tag_cloud_html5", "options_sidebar"),true);
+ $options["deadZone"] = $options["deadZone"];
+ $options["shape"] = "sphere";
+ $options["lock"] = "";
+ $options["stretchX"] = 1.0;
+ $options["stretchY"] = 1.0;
+ $options["decel"] = 0.92;
+ $options["physModel"] = true;
+ $options["maxInputZone"] = 0.25;
+ $options["minSpeed"] = 0.002;
+ $options["initialDecel"] = true;
+ module::set_var("tag_cloud_html5", "options_sidebar", json_encode($options));
+
+ $options = json_decode(module::get_var("tag_cloud_html5", "options_wholecloud"),true);
+ $options["deadZone"] = $options["deadZone"];
+ $options["shape"] = "sphere";
+ $options["lock"] = "";
+ $options["stretchX"] = 1.0;
+ $options["stretchY"] = 1.0;
+ $options["decel"] = 0.92;
+ $options["physModel"] = true;
+ $options["maxInputZone"] = 0.15;
+ $options["minSpeed"] = 0.002;
+ $options["initialDecel"] = true;
+ module::set_var("tag_cloud_html5", "options_wholecloud", json_encode($options));
+ }
+ // note: there are no variable changes for v6 and v7 upgrades
+ module::set_version("tag_cloud_html5", 7);
+ }
+
+ static function uninstall() {
+ module::clear_all_vars("tag_cloud_html5");
+ }
+
+}
diff --git a/modules/tag_cloud_html5/helpers/tag_cloud_html5_theme.php b/modules/tag_cloud_html5/helpers/tag_cloud_html5_theme.php new file mode 100644 index 0000000..ce0b6d3 --- /dev/null +++ b/modules/tag_cloud_html5/helpers/tag_cloud_html5_theme.php @@ -0,0 +1,25 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 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 tag_cloud_html5_theme_Core { + static function head($theme) { + $theme->script("jquery.tagcanvas.mod.min.js"); + $theme->css("tag_cloud_html5.css"); + } +}
\ No newline at end of file diff --git a/modules/tag_cloud_html5/js/excanvas.compiled.js b/modules/tag_cloud_html5/js/excanvas.compiled.js new file mode 100644 index 0000000..a34ca1d --- /dev/null +++ b/modules/tag_cloud_html5/js/excanvas.compiled.js @@ -0,0 +1,35 @@ +// Copyright 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +document.createElement("canvas").getContext||(function(){var s=Math,j=s.round,F=s.sin,G=s.cos,V=s.abs,W=s.sqrt,k=10,v=k/2;function X(){return this.context_||(this.context_=new H(this))}var L=Array.prototype.slice;function Y(b,a){var c=L.call(arguments,2);return function(){return b.apply(a,c.concat(L.call(arguments)))}}var M={init:function(b){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var a=b||document;a.createElement("canvas");a.attachEvent("onreadystatechange",Y(this.init_,this,a))}},init_:function(b){b.namespaces.g_vml_|| +b.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML");b.namespaces.g_o_||b.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML");if(!b.styleSheets.ex_canvas_){var a=b.createStyleSheet();a.owningElement.id="ex_canvas_";a.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}g_o_\\:*{behavior:url(#default#VML)}"}var c=b.getElementsByTagName("canvas"),d=0;for(;d<c.length;d++)this.initElement(c[d])}, +initElement:function(b){if(!b.getContext){b.getContext=X;b.innerHTML="";b.attachEvent("onpropertychange",Z);b.attachEvent("onresize",$);var a=b.attributes;if(a.width&&a.width.specified)b.style.width=a.width.nodeValue+"px";else b.width=b.clientWidth;if(a.height&&a.height.specified)b.style.height=a.height.nodeValue+"px";else b.height=b.clientHeight}return b}};function Z(b){var a=b.srcElement;switch(b.propertyName){case "width":a.style.width=a.attributes.width.nodeValue+"px";a.getContext().clearRect(); +break;case "height":a.style.height=a.attributes.height.nodeValue+"px";a.getContext().clearRect();break}}function $(b){var a=b.srcElement;if(a.firstChild){a.firstChild.style.width=a.clientWidth+"px";a.firstChild.style.height=a.clientHeight+"px"}}M.init();var N=[],B=0;for(;B<16;B++){var C=0;for(;C<16;C++)N[B*16+C]=B.toString(16)+C.toString(16)}function I(){return[[1,0,0],[0,1,0],[0,0,1]]}function y(b,a){var c=I(),d=0;for(;d<3;d++){var f=0;for(;f<3;f++){var h=0,g=0;for(;g<3;g++)h+=b[d][g]*a[g][f];c[d][f]= +h}}return c}function O(b,a){a.fillStyle=b.fillStyle;a.lineCap=b.lineCap;a.lineJoin=b.lineJoin;a.lineWidth=b.lineWidth;a.miterLimit=b.miterLimit;a.shadowBlur=b.shadowBlur;a.shadowColor=b.shadowColor;a.shadowOffsetX=b.shadowOffsetX;a.shadowOffsetY=b.shadowOffsetY;a.strokeStyle=b.strokeStyle;a.globalAlpha=b.globalAlpha;a.arcScaleX_=b.arcScaleX_;a.arcScaleY_=b.arcScaleY_;a.lineScale_=b.lineScale_}function P(b){var a,c=1;b=String(b);if(b.substring(0,3)=="rgb"){var d=b.indexOf("(",3),f=b.indexOf(")",d+ +1),h=b.substring(d+1,f).split(",");a="#";var g=0;for(;g<3;g++)a+=N[Number(h[g])];if(h.length==4&&b.substr(3,1)=="a")c=h[3]}else a=b;return{color:a,alpha:c}}function aa(b){switch(b){case "butt":return"flat";case "round":return"round";case "square":default:return"square"}}function H(b){this.m_=I();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.fillStyle=this.strokeStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=k*1;this.globalAlpha=1;this.canvas=b; +var a=b.ownerDocument.createElement("div");a.style.width=b.clientWidth+"px";a.style.height=b.clientHeight+"px";a.style.overflow="hidden";a.style.position="absolute";b.appendChild(a);this.element_=a;this.lineScale_=this.arcScaleY_=this.arcScaleX_=1}var i=H.prototype;i.clearRect=function(){this.element_.innerHTML=""};i.beginPath=function(){this.currentPath_=[]};i.moveTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"moveTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y}; +i.lineTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"lineTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};i.bezierCurveTo=function(b,a,c,d,f,h){var g=this.getCoords_(f,h),l=this.getCoords_(b,a),e=this.getCoords_(c,d);Q(this,l,e,g)};function Q(b,a,c,d){b.currentPath_.push({type:"bezierCurveTo",cp1x:a.x,cp1y:a.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y});b.currentX_=d.x;b.currentY_=d.y}i.quadraticCurveTo=function(b,a,c,d){var f=this.getCoords_(b,a),h=this.getCoords_(c,d),g={x:this.currentX_+ +0.6666666666666666*(f.x-this.currentX_),y:this.currentY_+0.6666666666666666*(f.y-this.currentY_)};Q(this,g,{x:g.x+(h.x-this.currentX_)/3,y:g.y+(h.y-this.currentY_)/3},h)};i.arc=function(b,a,c,d,f,h){c*=k;var g=h?"at":"wa",l=b+G(d)*c-v,e=a+F(d)*c-v,m=b+G(f)*c-v,r=a+F(f)*c-v;if(l==m&&!h)l+=0.125;var n=this.getCoords_(b,a),o=this.getCoords_(l,e),q=this.getCoords_(m,r);this.currentPath_.push({type:g,x:n.x,y:n.y,radius:c,xStart:o.x,yStart:o.y,xEnd:q.x,yEnd:q.y})};i.rect=function(b,a,c,d){this.moveTo(b, +a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath()};i.strokeRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.stroke();this.currentPath_=f};i.fillRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.fill();this.currentPath_=f};i.createLinearGradient=function(b, +a,c,d){var f=new D("gradient");f.x0_=b;f.y0_=a;f.x1_=c;f.y1_=d;return f};i.createRadialGradient=function(b,a,c,d,f,h){var g=new D("gradientradial");g.x0_=b;g.y0_=a;g.r0_=c;g.x1_=d;g.y1_=f;g.r1_=h;return g};i.drawImage=function(b){var a,c,d,f,h,g,l,e,m=b.runtimeStyle.width,r=b.runtimeStyle.height;b.runtimeStyle.width="auto";b.runtimeStyle.height="auto";var n=b.width,o=b.height;b.runtimeStyle.width=m;b.runtimeStyle.height=r;if(arguments.length==3){a=arguments[1];c=arguments[2];h=g=0;l=d=n;e=f=o}else if(arguments.length== +5){a=arguments[1];c=arguments[2];d=arguments[3];f=arguments[4];h=g=0;l=n;e=o}else if(arguments.length==9){h=arguments[1];g=arguments[2];l=arguments[3];e=arguments[4];a=arguments[5];c=arguments[6];d=arguments[7];f=arguments[8]}else throw Error("Invalid number of arguments");var q=this.getCoords_(a,c),t=[];t.push(" <g_vml_:group",' coordsize="',k*10,",",k*10,'"',' coordorigin="0,0"',' style="width:',10,"px;height:",10,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]){var E=[];E.push("M11=", +this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",j(q.x/k),",","Dy=",j(q.y/k),"");var p=q,z=this.getCoords_(a+d,c),w=this.getCoords_(a,c+f),x=this.getCoords_(a+d,c+f);p.x=s.max(p.x,z.x,w.x,x.x);p.y=s.max(p.y,z.y,w.y,x.y);t.push("padding:0 ",j(p.x/k),"px ",j(p.y/k),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",E.join(""),", sizingmethod='clip');")}else t.push("top:",j(q.y/k),"px;left:",j(q.x/k),"px;");t.push(' ">','<g_vml_:image src="',b.src, +'"',' style="width:',k*d,"px;"," height:",k*f,'px;"',' cropleft="',h/n,'"',' croptop="',g/o,'"',' cropright="',(n-h-l)/n,'"',' cropbottom="',(o-g-e)/o,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",t.join(""))};i.stroke=function(b){var a=[],c=P(b?this.fillStyle:this.strokeStyle),d=c.color,f=c.alpha*this.globalAlpha;a.push("<g_vml_:shape",' filled="',!!b,'"',' style="position:absolute;width:',10,"px;height:",10,'px;"',' coordorigin="0 0" coordsize="',k*10," ",k*10,'"',' stroked="', +!b,'"',' path="');var h={x:null,y:null},g={x:null,y:null},l=0;for(;l<this.currentPath_.length;l++){var e=this.currentPath_[l];switch(e.type){case "moveTo":a.push(" m ",j(e.x),",",j(e.y));break;case "lineTo":a.push(" l ",j(e.x),",",j(e.y));break;case "close":a.push(" x ");e=null;break;case "bezierCurveTo":a.push(" c ",j(e.cp1x),",",j(e.cp1y),",",j(e.cp2x),",",j(e.cp2y),",",j(e.x),",",j(e.y));break;case "at":case "wa":a.push(" ",e.type," ",j(e.x-this.arcScaleX_*e.radius),",",j(e.y-this.arcScaleY_*e.radius), +" ",j(e.x+this.arcScaleX_*e.radius),",",j(e.y+this.arcScaleY_*e.radius)," ",j(e.xStart),",",j(e.yStart)," ",j(e.xEnd),",",j(e.yEnd));break}if(e){if(h.x==null||e.x<h.x)h.x=e.x;if(g.x==null||e.x>g.x)g.x=e.x;if(h.y==null||e.y<h.y)h.y=e.y;if(g.y==null||e.y>g.y)g.y=e.y}}a.push(' ">');if(b)if(typeof this.fillStyle=="object"){var m=this.fillStyle,r=0,n={x:0,y:0},o=0,q=1;if(m.type_=="gradient"){var t=m.x1_/this.arcScaleX_,E=m.y1_/this.arcScaleY_,p=this.getCoords_(m.x0_/this.arcScaleX_,m.y0_/this.arcScaleY_), +z=this.getCoords_(t,E);r=Math.atan2(z.x-p.x,z.y-p.y)*180/Math.PI;if(r<0)r+=360;if(r<1.0E-6)r=0}else{var p=this.getCoords_(m.x0_,m.y0_),w=g.x-h.x,x=g.y-h.y;n={x:(p.x-h.x)/w,y:(p.y-h.y)/x};w/=this.arcScaleX_*k;x/=this.arcScaleY_*k;var R=s.max(w,x);o=2*m.r0_/R;q=2*m.r1_/R-o}var u=m.colors_;u.sort(function(ba,ca){return ba.offset-ca.offset});var J=u.length,da=u[0].color,ea=u[J-1].color,fa=u[0].alpha*this.globalAlpha,ga=u[J-1].alpha*this.globalAlpha,S=[],l=0;for(;l<J;l++){var T=u[l];S.push(T.offset*q+ +o+" "+T.color)}a.push('<g_vml_:fill type="',m.type_,'"',' method="none" focus="100%"',' color="',da,'"',' color2="',ea,'"',' colors="',S.join(","),'"',' opacity="',ga,'"',' g_o_:opacity2="',fa,'"',' angle="',r,'"',' focusposition="',n.x,",",n.y,'" />')}else a.push('<g_vml_:fill color="',d,'" opacity="',f,'" />');else{var K=this.lineScale_*this.lineWidth;if(K<1)f*=K;a.push("<g_vml_:stroke",' opacity="',f,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',aa(this.lineCap), +'"',' weight="',K,'px"',' color="',d,'" />')}a.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",a.join(""))};i.fill=function(){this.stroke(true)};i.closePath=function(){this.currentPath_.push({type:"close"})};i.getCoords_=function(b,a){var c=this.m_;return{x:k*(b*c[0][0]+a*c[1][0]+c[2][0])-v,y:k*(b*c[0][1]+a*c[1][1]+c[2][1])-v}};i.save=function(){var b={};O(this,b);this.aStack_.push(b);this.mStack_.push(this.m_);this.m_=y(I(),this.m_)};i.restore=function(){O(this.aStack_.pop(), +this);this.m_=this.mStack_.pop()};function ha(b){var a=0;for(;a<3;a++){var c=0;for(;c<2;c++)if(!isFinite(b[a][c])||isNaN(b[a][c]))return false}return true}function A(b,a,c){if(!!ha(a)){b.m_=a;if(c)b.lineScale_=W(V(a[0][0]*a[1][1]-a[0][1]*a[1][0]))}}i.translate=function(b,a){A(this,y([[1,0,0],[0,1,0],[b,a,1]],this.m_),false)};i.rotate=function(b){var a=G(b),c=F(b);A(this,y([[a,c,0],[-c,a,0],[0,0,1]],this.m_),false)};i.scale=function(b,a){this.arcScaleX_*=b;this.arcScaleY_*=a;A(this,y([[b,0,0],[0,a, +0],[0,0,1]],this.m_),true)};i.transform=function(b,a,c,d,f,h){A(this,y([[b,a,0],[c,d,0],[f,h,1]],this.m_),true)};i.setTransform=function(b,a,c,d,f,h){A(this,[[b,a,0],[c,d,0],[f,h,1]],true)};i.clip=function(){};i.arcTo=function(){};i.createPattern=function(){return new U};function D(b){this.type_=b;this.r1_=this.y1_=this.x1_=this.r0_=this.y0_=this.x0_=0;this.colors_=[]}D.prototype.addColorStop=function(b,a){a=P(a);this.colors_.push({offset:b,color:a.color,alpha:a.alpha})};function U(){}G_vmlCanvasManager= +M;CanvasRenderingContext2D=H;CanvasGradient=D;CanvasPattern=U})(); diff --git a/modules/tag_cloud_html5/js/jquery.tagcanvas.mod.js b/modules/tag_cloud_html5/js/jquery.tagcanvas.mod.js new file mode 100644 index 0000000..016a291 --- /dev/null +++ b/modules/tag_cloud_html5/js/jquery.tagcanvas.mod.js @@ -0,0 +1,1014 @@ +/** + * Copyright (C) 2010-2012 Graham Breach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +/** + * jQuery.tagcanvas 1.17.1 + * For more information, please contact <graham@goat1000.com> + */ +/** + * Modified by Shad Laws 2012/06/08 -- all modified lines have "mod Shad Laws" comments + * - built a physics-based model for motion, which is activated with the physModel flag + * - included initialDecel, deadZone, maxInputZone, and physModel as options + * - set defaults of new options to mimic behavior without them (false, 0, 0, and false) + * - removed two unnecessary variable declarations caught by YUI Compressor + * - fixed initialization of a few variables (minSpeed, decel, yaw/pitch) + * - fixed problem with html margin-top changing coordinates in IE (but not Chrome or Firefox) + */ +(function($) { +// var i, j, abs = Math.abs, sin = Math.sin, cos = Math.cos, max = Math.max, min = Math.min, // mod Shad Laws +var i, j, abs = Math.abs, sin = Math.sin, cos = Math.cos, max = Math.max, min = Math.min, sqrt = Math.sqrt, // mod Shad Laws + hexlookup3 = {}, hexlookup2 = {}, hexlookup1 = { + 0:"0,", 1:"17,", 2:"34,", 3:"51,", 4:"68,", 5:"85,", + 6:"102,", 7:"119,", 8:"136,", 9:"153,", a:"170,", A:"170,", + b:"187,", B:"187,", c:"204,", C:"204,", d:"221,", D:"221,", + e:"238,", E:"238,", f:"255,", F:"255," +}, Oproto, Tproto, TCproto, doc = document, ocanvas, handlers = {}; +for(i = 0; i < 256; ++i) { + j = i.toString(16); + if(i < 16) + j = '0' + j; + hexlookup2[j] = hexlookup2[j.toUpperCase()] = i.toString() + ','; +} +function Defined(d) { + return typeof(d) != 'undefined'; +} +function Shuffle(a) { + var i = a.length-1, t, p; + while(i) { + p = ~~(Math.random()*i); + t = a[i]; + a[i] = a[p]; + a[p] = t; + --i; + } +} +function PointsOnSphere(n,xr,yr,zr) { + var i, y, r, phi, pts = [], inc = Math.PI * (3-Math.sqrt(5)), off = 2/n; + for(i = 0; i < n; ++i) { + y = i * off - 1 + (off / 2); + r = Math.sqrt(1 - y*y); + phi = i * inc; + pts.push([cos(phi) * r * xr, y * yr, sin(phi) * r * zr]); + } + return pts; +} +function Cylinder(n,o,xr,yr,zr,i,j,k,l) { + var phi, pts = [], inc = Math.PI * (3-Math.sqrt(5)), off = 2/n; + for(i = 0; i < n; ++i) { + j = i * off - 1 + (off / 2); + phi = i * inc; + k = cos(phi); + l = sin(phi); + pts.push(o ? [j * xr, k * yr, l * zr] : [k * xr, j * yr, l * zr]); + } + return pts; +} +function PointsOnCylinderV(n,xr,yr,zr) { return Cylinder(n, 0, xr, yr, zr) } +function PointsOnCylinderH(n,xr,yr,zr) { return Cylinder(n, 1, xr, yr, zr) } +function SetAlpha(c,a) { + var d = c, p1, p2, ae = (a*1).toPrecision(3) + ')'; + if(c[0] === '#') { + if(!hexlookup3[c]) + if(c.length === 4) + hexlookup3[c] = 'rgba(' + hexlookup1[c[1]] + hexlookup1[c[2]] + hexlookup1[c[3]]; + else + hexlookup3[c] = 'rgba(' + hexlookup2[c.substr(1,2)] + hexlookup2[c.substr(3,2)] + hexlookup2[c.substr(5,2)]; + d = hexlookup3[c] + ae; + } else if(c.substr(0,4) === 'rgb(' || c.substr(0,4) === 'hsl(') { + d = (c.replace('(','a(').replace(')', ',' + ae)); + } else if(c.substr(0,5) === 'rgba(' || c.substr(0,5) === 'hsla(') { + p1 = c.lastIndexOf(',') + 1, p2 = c.indexOf(')'); + a *= parseFloat(c.substring(p1,p2)); + d = c.substr(0,p1) + a.toPrecision(3) + ')'; + } + return d; +} +function NewCanvas(w,h) { + // if using excanvas, give up now + if(window.G_vmlCanvasManager) + return null; + var c = doc.createElement('canvas'); + c.width = w; + c.height = h; + return c; +} +// I think all browsers pass this test now... +function ShadowAlphaBroken() { + var cv = NewCanvas(3,3), c, i; + if(!cv) + return false; + c = cv.getContext('2d'); + c.strokeStyle = '#000'; + c.shadowColor = '#fff'; + c.shadowBlur = 3; + c.globalAlpha = 0; + c.strokeRect(2,2,2,2); + c.globalAlpha = 1; + i = c.getImageData(2,2,1,1); + cv = null; + return (i.data[0] > 0); +} +function FindGradientColour(t,p) { + var l = 1024, g = t.weightGradient, cv, c, i, gd, d; + if(t.gCanvas) { + c = t.gCanvas.getContext('2d'); + } else { + t.gCanvas = cv = NewCanvas(l,1); + if(!cv) + return null; + c = cv.getContext('2d'); + gd = c.createLinearGradient(0,0,l,0); + for(i in g) + gd.addColorStop(1-i, g[i]); + c.fillStyle = gd; + c.fillRect(0,0,l,1); + } + d = c.getImageData(~~((l-1)*p),0,1,1).data; + return 'rgba(' + d[0] + ',' + d[1] + ',' + d[2] + ',' + (d[3]/255) + ')'; +} +function TextSet(c,f,l,s,sc,sb,so) { + var xo = (sb || 0) + (so && so[0] < 0 ? abs(so[0]) : 0), + yo = (sb || 0) + (so && so[1] < 0 ? abs(so[1]) : 0); + c.font = f; + c.textBaseline = 'top'; + c.fillStyle = l; + sc && (c.shadowColor = sc); + sb && (c.shadowBlur = sb); + so && (c.shadowOffsetX = so[0], c.shadowOffsetY = so[1]); + c.fillText(s, xo, yo); +} +function TextToCanvas(s,f,ht,w,h,l,sc,sb,so,padx,pady) { + var cw = w + abs(so[0]) + sb + sb, ch = h + abs(so[1]) + sb + sb, cv, c; + cv = NewCanvas(cw+padx,ch+pady); + if(!cv) + return null; + c = cv.getContext('2d'); + TextSet(c,f,l,s,sc,sb,so); + return cv; +} +function AddShadowToImage(i,sc,sb,so) { + var cw = i.width + abs(so[0]) + sb + sb, ch = i.height + abs(so[1]) + sb + sb, cv, c, + xo = (sb || 0) + (so && so[0] < 0 ? abs(so[0]) : 0), + yo = (sb || 0) + (so && so[1] < 0 ? abs(so[1]) : 0); + cv = NewCanvas(cw,ch); + if(!cv) + return null; + c = cv.getContext('2d'); + sc && (c.shadowColor = sc); + sb && (c.shadowBlur = sb); + so && (c.shadowOffsetX = so[0], c.shadowOffsetY = so[1]); + c.drawImage(i, xo, yo); + return cv; +} +function FindTextBoundingBox(s,f,ht) { + var w = parseInt(s.length * ht), h = parseInt(ht * 2), cv = NewCanvas(w,h), c, idata, w1, h1, x, y, i, ex; + if(!cv) + return null; + c = cv.getContext('2d'); + c.fillStyle = '#000'; + c.fillRect(0,0,w,h); + TextSet(c,ht + 'px ' + f,'#fff',s) + + idata = c.getImageData(0,0,w,h); + w1 = idata.width; h1 = idata.height; + ex = { + min: { x: w1, y: h1 }, + max: { x: -1, y: -1 } + }; + for(y = 0; y < h1; ++y) { + for(x = 0; x < w1; ++x) { + i = (y * w1 + x) * 4; + if(idata.data[i+1] > 0) { + if(x < ex.min.x) ex.min.x = x; + if(x > ex.max.x) ex.max.x = x; + if(y < ex.min.y) ex.min.y = y; + if(y > ex.max.y) ex.max.y = y; + } + } + } + // device pixels might not be css pixels + if(w1 != w) { + ex.min.x *= (w / w1); + ex.max.x *= (w / w1); + } + if(h1 != h) { + ex.min.y *= (w / h1); + ex.max.y *= (w / h1); + } + + cv = null; + return ex; +} +function FixFont(f) { + return "'" + f.replace(/(\'|\")/g,'').replace(/\s*,\s*/g, "', '") + "'"; +} +function AddHandler(h,f,e) { + e = e || doc; + if(e.addEventListener) + e.addEventListener(h,f,false); + else + e.attachEvent('on' + h, f); +} +function AddImage(i,o,t,tc) { + var tl = tc.taglist, s = tc.imageScale; + if(s && !(o.width && o.height)) { + // images are not yet rendered, wait for window onload + AddHandler('load', function() { AddImage(i,o,t,tc); }, window); + return; + } + if(!i.complete) { + // image not loaded, wait for image onload + AddHandler('load',function() { AddImage(i,o,t,tc); }, i); + return; + } + + // Yes, this does look like nonsense, but it makes sure that both the + // width and height are actually set and not just calculated. This is + // required to keep proportional sizes when the images are hidden, so + // the images can be used again for another cloud. + o.width = o.width; + o.height = o.height; + + if(s) { + i.width = o.width * s; + i.height = o.height * s; + } + t.w = i.width; + t.h = i.height; + tl.push(t); +} +function GetProperty(e,p) { + var dv = doc.defaultView, pc = p.replace(/\-([a-z])/g,function(a){return a.charAt(1).toUpperCase()}); + return (dv && dv.getComputedStyle && dv.getComputedStyle(e,null).getPropertyValue(p)) || + (e.currentStyle && e.currentStyle[pc]); +} +function FindWeight(t,a) { + var w = 1, p; + if(t.weightFrom) { + w = 1 * (a.getAttribute(t.weightFrom) || t.textHeight); + } else if(p = GetProperty(a,'font-size')) { + w = (p.indexOf('px') > -1 && p.replace('px','') * 1) || + (p.indexOf('pt') > -1 && p.replace('pt','') * 1.25) || + p * 3.3; + } else { + t.weight = false; + } + return w; +} +function MouseOut(e) { + MouseMove(e); + var cv = e.target || e.fromElement.parentNode, tc = TagCanvas.tc[cv.id]; + tc && (tc.mx = tc.my = -1); +} +function MouseMove(e) { + var i, tc, dd = doc.documentElement, o; + for(i in TagCanvas.tc) { + tc = TagCanvas.tc[i]; + if(tc.tttimer) { + clearTimeout(tc.tttimer); + tc.tttimer = null; + } + o = $(tc.canvas).offset(); + // if(e.pageX) { // mod Shad Laws + if(e.offsetX) { // mod Shad Laws + // this works for IE + tc.mx = e.offsetX; // mod Shad Laws + tc.my = e.offsetY; // mod Shad Laws + } else if(e.pageX) { // mod Shad Laws + // this doesn't work for IE --> e.pageY = -parseInt($("html").css("margin-left"), 10) for the top row! + tc.mx = e.pageX - o.left; + tc.my = e.pageY - o.top; + } else { + tc.mx = e.clientX + (dd.scrollLeft || doc.body.scrollLeft) - o.left; + tc.my = e.clientY + (dd.scrollTop || doc.body.scrollTop) - o.top; + } + } +} +function MouseClick(e) { + var t = TagCanvas, cb = doc.addEventListener ? 0 : 1, + tg = e.target && Defined(e.target.id) ? e.target.id : e.srcElement.parentNode.id; + if(tg && e.button == cb && t.tc[tg]) { + MouseMove(e); + t.tc[tg].Clicked(e); + } +} +function MouseWheel(e) { + var t = TagCanvas, + tg = e.target && Defined(e.target.id) ? e.target.id : e.srcElement.parentNode.id; + if(tg && t.tc[tg]) { + e.cancelBubble = true; + e.returnValue = false; + e.preventDefault && e.preventDefault(); + t.tc[tg].Wheel((e.wheelDelta || e.detail) > 0); + } +} +function DrawCanvas() { + var t = TagCanvas.tc, i; + for(i in t) + t[i].Draw(); +} +function RotX(p1,t) { + var s = sin(t), c = cos(t); + return {x:p1.x, y:(p1.y * c) + (p1.z * s), z:(p1.y * -s) + (p1.z * c)}; +} +function RotY(p1,t) { + var s = sin(t), c = cos(t); + return {x:(p1.x * c) + (p1.z * -s), y:p1.y, z:(p1.x * s) + (p1.z * c)}; +} +function Project(tc,p1,w,h,sx,sy) { + var yn, xn, zn, m = tc.z1 / (tc.z1 + tc.z2 + p1.z); + yn = p1.y * m * sy; + xn = p1.x * m * sx; + zn = tc.z2 + p1.z; + return {x:xn, y:yn, z:zn}; +} +/** + * @constructor + */ +function Outline(tc) { + this.ts = new Date().valueOf(); + this.tc = tc; + this.x = this.y = this.w = this.h = this.sc = 1; + this.z = 0; + this.Draw = tc.pulsateTo < 1 && tc.outlineMethod != 'colour' ? this.DrawPulsate : this.DrawSimple; + this.SetMethod(tc.outlineMethod); +} +Oproto = Outline.prototype; +Oproto.SetMethod = function(om) { + var methods = { + block: ['PreDraw','DrawBlock'], + colour: ['PreDraw','DrawColour'], + outline: ['PostDraw','DrawOutline'], + classic: ['LastDraw','DrawOutline'], + none: ['LastDraw'] + }, funcs = methods[om] || methods.outline; + if(om == 'none') { + this.Draw = function() { return 1; } + } else { + this.drawFunc = this[funcs[1]]; + } + this[funcs[0]] = this.Draw; +}; +Oproto.Update = function(x,y,w,h,sc,p,xo,yo) { + var o = this.tc.outlineOffset, o2 = 2 * o; + this.x = sc * x + xo - o; + this.y = sc * y + yo - o; + this.w = sc * w + o2; + this.h = sc * h + o2; + this.sc = sc; // used to determine frontmost + this.z = p.z; +}; +Oproto.DrawOutline = function(c,x,y,w,h,colour) { + c.strokeStyle = colour; + c.strokeRect(x,y,w,h); +}; +Oproto.DrawColour = function(c,x,y,w,h,colour,tag,x1,y1) { + return this[tag.image ? 'DrawColourImage' : 'DrawColourText'](c,x,y,w,h,colour,tag,x1,y1); +}; +Oproto.DrawColourText = function(c,x,y,w,h,colour,tag,x1,y1) { + var normal = tag.colour; + tag.colour = colour; + tag.Draw(c,x1,y1); + tag.colour = normal; + return 1; +}; +Oproto.DrawColourImage = function(c,x,y,w,h,colour,tag,x1,y1) { + var ccanvas = c.canvas, fx = ~~max(x,0), fy = ~~max(y,0), + fw = min(ccanvas.width - fx, w) + .5|0, fh = min(ccanvas.height - fy,h) + .5|0, cc; + if(ocanvas) + ocanvas.width = fw, ocanvas.height = fh; + else + ocanvas = NewCanvas(fw, fh); + if(!ocanvas) + return this.SetMethod('outline'); // if using IE and images, give up! + cc = ocanvas.getContext('2d'); + + cc.drawImage(ccanvas,fx,fy,fw,fh,0,0,fw,fh); + c.clearRect(fx,fy,fw,fh); + tag.Draw(c,x1,y1); + c.setTransform(1,0,0,1,0,0); + c.save(); + c.beginPath(); + c.rect(fx,fy,fw,fh); + c.clip(); + c.globalCompositeOperation = 'source-in'; + c.fillStyle = colour; + c.fillRect(fx,fy,fw,fh); + c.restore(); + c.globalCompositeOperation = 'destination-over'; + c.drawImage(ocanvas,0,0,fw,fh,fx,fy,fw,fh); + c.globalCompositeOperation = 'source-over'; + return 1; +}; +Oproto.DrawBlock = function(c,x,y,w,h,colour) { + c.fillStyle = colour; + c.fillRect(x,y,w,h); +}; +Oproto.DrawSimple = function(c, tag, x1, y1) { + var t = this.tc; + c.setTransform(1,0,0,1,0,0); + c.strokeStyle = t.outlineColour; + c.lineWidth = t.outlineThickness; + c.shadowBlur = c.shadowOffsetX = c.shadowOffsetY = 0; + c.globalAlpha = 1; + return this.drawFunc(c,this.x,this.y,this.w,this.h,t.outlineColour,tag,x1,y1); +}; +Oproto.DrawPulsate = function(c, tag, x1, y1) { + var diff = new Date().valueOf() - this.ts, t = this.tc; + c.setTransform(1,0,0,1,0,0); + c.strokeStyle = t.outlineColour; + c.lineWidth = t.outlineThickness; + c.shadowBlur = c.shadowOffsetX = c.shadowOffsetY = 0; + c.globalAlpha = t.pulsateTo + ((1 - t.pulsateTo) * + (0.5 + (cos(2 * Math.PI * diff / (1000 * t.pulsateTime)) / 2))); + return this.drawFunc(c,this.x,this.y,this.w,this.h,t.outlineColour,tag,x1,y1); +}; +Oproto.Active = function(c,x,y) { + return (x >= this.x && y >= this.y && + x <= this.x + this.w && y <= this.y + this.h); +}; +Oproto.PreDraw = Oproto.PostDraw = Oproto.LastDraw = function() {}; +/** + * @constructor + */ +function Tag(tc,name,a,v,w,h,col,font) { + var c = tc.ctxt, i; + this.tc = tc; + this.image = name.src ? name : null; + this.name = name.src ? '' : name; + this.title = a.title || null; + this.a = a; + this.p3d = { x: v[0] * tc.radius * 1.1, y: v[1] * tc.radius * 1.1, z: v[2] * tc.radius * 1.1}; + this.x = this.y = 0; + this.w = w; + this.h = h; + this.colour = col || tc.textColour; + this.textFont = font || tc.textFont; + this.weight = this.sc = this.alpha = 1; + this.weighted = !tc.weight; + this.outline = new Outline(tc); + if(this.image) { + if(tc.txtOpt && tc.shadow) { + i = AddShadowToImage(this.image,tc.shadow,tc.shadowBlur,tc.shadowOffset); + if(i) { + this.image = i; + this.w = i.width; + this.h = i.height; + } + } + } else { + this.textHeight = tc.textHeight; + this.extents = FindTextBoundingBox(this.name, this.textFont, this.textHeight); + this.Measure(c,tc); + } + this.SetShadowColour = tc.shadowAlpha ? this.SetShadowColourAlpha : this.SetShadowColourFixed; + this.SetDraw(tc); +} +Tproto = Tag.prototype; +Tproto.SetDraw = function(t) { + this.Draw = this.image ? (t.ie > 7 ? this.DrawImageIE : this.DrawImage) : this.DrawText; + t.noSelect && (this.CheckActive = function() {}); +}; +Tproto.Measure = function(c,t) { + this.h = this.extents ? this.extents.max.y + this.extents.min.y : this.textHeight; + c.font = this.font = this.textHeight + 'px ' + this.textFont; + this.w = c.measureText(this.name).width; + if(t.txtOpt) { + var s = t.txtScale, th = s * this.textHeight, f = th + 'px ' + this.textFont, + soff = [s*t.shadowOffset[0],s*t.shadowOffset[1]], cw; + c.font = f; + cw = c.measureText(this.name).width; + this.image = TextToCanvas(this.name, f, th, cw, s * this.h, this.colour, + t.shadow, s * t.shadowBlur, soff, s, s); + if(this.image) { + this.w = this.image.width / s; + this.h = this.image.height / s; + } + this.SetDraw(t); + t.txtOpt = this.image; + } +}; +Tproto.SetWeight = function(w) { + if(!this.name.length) + return; + this.weight = w; + this.Weight(this.tc.ctxt, this.tc); + this.Measure(this.tc.ctxt, this.tc); +}; +Tproto.Weight = function(c,t) { + var w = this.weight, m = t.weightMode; + this.weighted = true; + if(m == 'colour' || m == 'both') + this.colour = FindGradientColour(t, (w - t.min_weight) / (t.max_weight-t.min_weight)); + if(m == 'size' || m == 'both') + this.textHeight = w * t.weightSize; + this.extents = FindTextBoundingBox(this.name, this.textFont, this.textHeight); +}; +Tproto.SetShadowColourFixed = function(c,s,a) { + c.shadowColor = s; +}; +Tproto.SetShadowColourAlpha = function(c,s,a) { + c.shadowColor = SetAlpha(s, a); +}; +Tproto.DrawText = function(c,xoff,yoff) { + var t = this.tc, x = this.x, y = this.y, w, h, s = this.sc; + c.globalAlpha = this.alpha; + c.setTransform(s,0,0,s,0,0); + c.fillStyle = this.colour; + t.shadow && this.SetShadowColour(c,t.shadow,this.alpha); + c.font = this.font; + w = this.w; + h = this.h; + x += (xoff / s) - (w / 2); + y += (yoff / s) - (h / 2); + c.fillText(this.name, x, y); +}; +Tproto.DrawImage = function(c,xoff,yoff) { + //var t = this.tc, x = this.x, y = this.y, s = this.sc, // mod Shad Laws + var x = this.x, y = this.y, s = this.sc, // mod Shad Laws + i = this.image, w = this.w, h = this.h, a = this.alpha, + shadow = this.shadow; + c.globalAlpha = a; + c.setTransform(s,0,0,s,0,0); + c.fillStyle = this.colour; + shadow && this.SetShadowColour(c,shadow,a); + x += (xoff / s) - (w / 2); + y += (yoff / s) - (h / 2); + c.drawImage(i, x, y, w, h); +}; +Tproto.DrawImageIE = function(c,xoff,yoff) { + var i = this.image, s = this.sc, + w = i.width = this.w*s, h = i.height = this.h * s, + x = (this.x*s) + xoff - (w/2), y = (this.y*s) + yoff - (h/2); + c.setTransform(1,0,0,1,0,0); + c.globalAlpha = this.alpha; + c.drawImage(i, x, y); +}; +Tproto.Calc = function(yaw,pitch) { + var pp = RotY(this.p3d,yaw), t = this.tc, mb = t.minBrightness, r = t.radius; + this.p3d = RotX(pp,pitch); + pp = Project(t, this.p3d, this.w, this.h, t.stretchX, t.stretchY); + this.x = pp.x; + this.y = pp.y; + this.sc = (t.z1 + t.z2 - pp.z) / t.z2; + this.alpha = max(mb,min(1,mb + 1 - ((pp.z - t.z2 + r) / (2 * r)))); +}; +Tproto.CheckActive = function(c,xoff,yoff) { + var t = this.tc, o = this.outline, + w = this.w, h = this.h, + x = this.x - w/2, y = this.y - h/2; + o.Update(x, y, w, h, this.sc, this.p3d, xoff, yoff); + return o.Active(c, t.mx, t.my) ? o : null; +}; +Tproto.Clicked = function(e) { + var a = this.a, t = a.target, h = a.href, evt; + if(t != '' && t != '_self') { + if(self.frames[t]) { + self.frames[t] = h; + } else{ + try { + if(top.frames[t]) { + top.frames[t] = h; + return; + } + } catch(err) { + // different domain/port/protocol? + } + window.open(h, t); + } + return; + } + if(doc.createEvent) { + evt = doc.createEvent('MouseEvents'); + evt.initMouseEvent('click', 1, 1, window, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null); + if(!a.dispatchEvent(evt)) + return; + } else if(a.fireEvent) { + if(!a.fireEvent('onclick')) + return; + } + doc.location = h; +}; +/** + * @constructor + */ +function TagCanvas() { + var i, opts = { + mx: -1, my: -1, + z1: 20000, z2: 20000, z0: 0.0002, + freezeActive: false, + activeCursor: 'pointer', + pulsateTo: 1, + pulsateTime: 3, + reverse: false, + depth: 0.5, + maxSpeed: 0.05, + minSpeed: 0, + decel: 0.95, + interval: 20, + initial: null, + initialDecel: false, // mod Shad Laws + deadZone: 0, // mod Shad Laws + physModel: false, // mod Shad Laws + maxInputZone: 0, // mod Shad Laws + hideTags: true, + minBrightness: 0.1, + outlineColour: '#ffff99', + outlineThickness: 2, + outlineOffset: 5, + outlineMethod: 'outline', + textColour: '#ff99ff', + textHeight: 15, + textFont: 'Helvetica, Arial, sans-serif', + shadow: '#000', + shadowBlur: 0, + shadowOffset: [0,0], + zoom: 1, + weight: false, + weightMode: 'size', + weightFrom: null, + weightSize: 1, + weightGradient: {0:'#f00', 0.33:'#ff0', 0.66:'#0f0', 1:'#00f'}, + txtOpt: true, + txtScale: 2, + frontSelect: false, + wheelZoom: true, + zoomMin: 0.3, + zoomMax: 3, + zoomStep: 0.05, + shape: 'sphere', + lock: null, + tooltip: null, + tooltipDelay: 300, + tooltipClass: 'tctooltip', + radiusX: 1, + radiusY: 1, + radiusZ: 1, + stretchX: 1, + stretchY: 1, + shuffleTags: false, + noSelect: false, + noMouse: false, + imageScale: 1 + }; + for(i in opts) + this[i] = opts[i]; + this.max_weight = 0; + this.min_weight = 200; +} +TCproto = TagCanvas.prototype; +TCproto.Draw = function() { + var cv = this.canvas, cw = cv.width, ch = cv.height, max_sc = 0, yaw = this.yaw, pitch = this.pitch, + x1 = cw / 2, y1 = ch / 2, c = this.ctxt, active, a, i, aindex = -1, tl = this.taglist, l = tl.length, + frontsel = this.frontSelect; + //if(yaw == 0 && pitch == 0 && this.drawn) // mod Shad Laws + if(yaw == 0 && pitch == 0 && this.drawn && !this.zoneActive) // mod Shad Laws + return this.Animate(cw,ch); + c.setTransform(1,0,0,1,0,0); + this.active = null; + for(i = 0; i < l; ++i) + tl[i].Calc(yaw, pitch); + tl = tl.sort(function(a,b) {return a.sc-b.sc}); + + for(i = 0; i < l; ++i) { + a = tl[i].CheckActive(c, x1, y1); + a = this.mx >= 0 && this.my >= 0 && tl[i].CheckActive(c, x1, y1); + if(a && a.sc > max_sc && (!frontsel || a.z <= 0)) { + active = a; + active.index = aindex = i; + max_sc = a.sc; + } + } + this.active = active; + + if(!this.txtOpt && this.shadow) { + c.shadowBlur = this.shadowBlur; + c.shadowOffsetX = this.shadowOffset[0]; + c.shadowOffsetY = this.shadowOffset[1]; + } + c.clearRect(0,0,cw,ch); + for(i = 0; i < l; ++i) { + if(!(aindex == i && active.PreDraw(c, tl[i], x1, y1))) + tl[i].Draw(c, x1, y1); + aindex == i && active.PostDraw(c); + } + if(this.freezeActive && active) { + this.yaw = this.pitch = this.drawn = 0; + } else { + this.Animate(cw, ch); + this.drawn = (l == this.listLength); + } + active && active.LastDraw(c); + cv.style.cursor = active ? this.activeCursor : ''; + this.Tooltip(active,tl[aindex]); +}; +TCproto.TooltipNone = function() { }; +TCproto.TooltipNative = function(active,tag) { + this.canvas.title = active && tag.title ? tag.title : ''; +}; +TCproto.TooltipDiv = function(active,tag) { + //var tc = this, s = tc.ttdiv.style, cid = tc.canvas.id; // mod Shad Laws + var tc = this, s = tc.ttdiv.style; // mod Shad Laws + if(active && tag.title) { + tc.ttdiv.innerHTML = tag.title; + if(s.display == 'none' && ! tc.tttimer) { + tc.tttimer = setTimeout(function() { + var p = $(tc.canvas).offset(); + s.display = 'block'; + s.left = p.left + tc.mx + 'px'; + s.top = p.top + tc.my + 24 + 'px'; + tc.tttimer = null; + }, tc.tooltipDelay); + } + } else { + s.display = 'none'; + } +}; +TCproto.Animate = function(w,h) { // mod Shad Laws - original function afterward + var tc = this; // mod Shad Laws + if(tc.physModel) + /** + * Physics-based model -- mod Shad Laws + * State update equation + * (normalized inertia J=1 and time-step dt=1, forward Euler discretization) + * w1 = w - b*w + m*(u-f) + n*v + * where w1,w,u,f,v are vectors, b,m,n are scalars: + * w = rotational speed at t=k + * components wx, wy, magnitude w + * w1 = rotational speed at t=k+1 + * components wx1, wy1, magnitude w1 + * u-f = actual input from mouse position (-1 <= u-f <= 1) + * components ux, uy, magnitude u + * coulomb friction f (models "deadZone" effect) + * reversed/locked/saturated as defined (models "reverse", "lock", and "maxInputZone" effects) + * v = fictitious input (-1 <= v <= 1) + * components vx, vy, magnitude v + * directed as needed (models "minSpeed" effect) + * b = damping (models "decel" effect) + * m = maximum actual input (models "maxSpeed" effect) + * n = maximum fictitious input (models "minSpeed" effect) + */ + { + var x = tc.mx, y = tc.my, xmax = w-1, ymax = h-1, xstr = tc.stretchX, ystr = tc.stretchY; + var r = tc.reverse ? -1 : 1, l = tc.lock, usat = tc.maxInputZone, zt = tc.z0; + // calculate physical parameters (b,f,m,n) + var b = 1-tc.decel, f = tc.deadZone, m = tc.maxSpeed*b, n = tc.minSpeed*b; + // set rotational velocities, following previous convention where x-rotation is "pitch" and y-rotation is "yaw" + var wx = tc.pitch, wy = tc.yaw, w = sqrt(wx*wx + wy*wy), wx1, wy1, w1; + // calculate inputs + var ux, uy, u, vx, vy, v; + this.zoneActive = false; + if(x >= 0 && y >= 0 && x <= xmax && y <= ymax) + { + // the "max(xmax/ymax,1)" parts are needed to deal with non-square tag clouds correctly + ux = -r * (2 * y/ymax - 1) * max(ymax/xmax,1) / ystr; + uy = r * (2 * x/xmax - 1) * max(xmax/ymax,1) / xstr; + if(abs(ux) <= 1+usat && abs(uy) <= 1+usat) + { + ux = (l != 'y') ? ux : 0; + uy = (l != 'x') ? uy : 0; + u = sqrt(ux*ux + uy*uy); + if(u <= f || f >= 1-zt) // dead zone, zero input + { + this.initial = null; + this.zoneActive = true; + ux = 0, uy = 0, u = 0; + vx = 0, vy = 0, v = 0; + } + else if(u <= 1) // inside cloud, normal input + { + this.initial = null; + this.zoneActive = true; + ux = ux/u*(u-f)/(1-f), uy = uy/u*(u-f)/(1-f), u = (u-f)/(1-f); + vx = 0, vy = 0, v = 0; + } + else if(u <= 1+usat) // just outside cloud, saturated input + { + this.initial = null; + this.zoneActive = true; + ux = ux/u, uy = uy/u, u = 1; + vx = 0, vy = 0, v = 0; + } + } + } + if(!this.zoneActive) // outside cloud, fictitious input + { + ux = 0, uy = 0, u = 0; + if(w >= zt) + vx = wx/w, vy = wy/w, v = 1; + else + { + v = 2*Math.PI*Math.random(); + if(l == 'x') + vx = 0, vy = v<Math.PI ? 1 : -1, v = 1; + else if(l == 'y') + vx = v<Math.PI ? 1 : -1, vy = 0, v = 1; + else + vx = cos(v), vy = sin(v), v = 1; + } + } + // update state, if we aren't doing initial perpetual motion + if(!tc.initial || tc.initialDecel) + { + // main state update equation + wx1 = wx - b*wx + m*ux + n*vx; + wy1 = wy - b*wy + m*uy + n*vy; + w1 = sqrt(wx1*wx1 + wy1*wy1); + // account for zt + if(w1 < zt) + wx1 = 0, wy1 = 0, w1 = 0; + this.pitch = wx1; + this.yaw = wy1; + } + } + else // without physics-based model + { + var x = tc.mx, y = tc.my, l = tc.lock, s, ay, ap, r; // mod Shad Laws + tc.zoneActive = false; // mod Shad Laws + if(x >= 0 && y >= 0 && x < w && y < h) + { + tc.zoneActive = true; // mod Shad Laws + //s = tc.maxSpeed, r = tc.reverse ? -1 : 1; // mod Shad Laws + s = tc.maxSpeed, r = tc.reverse ? -1 : 1, dz = tc.deadZone; // mod Shad Laws + if(l != 'x') + //this.yaw = r * ((s * 2 * x / w) - s); // mod Shad Laws + this.yaw = r * s / max(1-dz,0.000001) * ( max(2*x/w-1-dz,0) + min(2*x/w-1+dz,0) ); // mod Shad Laws + if(l != 'y') + //this.pitch = r * -((s * 2 * y / h) - s); // mod Shad Laws + this.pitch = -r * s / max(1-dz,0.000001) * ( max(2*y/h-1-dz,0) + min(2*y/h-1+dz,0) ); // mod Shad Laws + this.initial = null; + } + //else if(!tc.initial) // mod Shad Laws + else if(!tc.initial || tc.initialDecel) // mod Shad Laws + { + s = tc.minSpeed, ay = abs(tc.yaw), ap = abs(tc.pitch); + if(l != 'x' && ay > s) + this.yaw = ay > tc.z0 ? tc.yaw * tc.decel : 0; + if(l != 'y' && ap > s) + this.pitch = ap > tc.z0 ? tc.pitch * tc.decel : 0; + } + } +}; +TCproto.Zoom = function(r) { + this.z2 = this.z1 * (1/r); + this.drawn = 0; +}; +TCproto.Clicked = function(e) { + var t = this.taglist, a = this.active; + try { + if(a && t[a.index]) + t[a.index].Clicked(e); + } catch(ex) { + } +}; +TCproto.Wheel = function(i) { + var z = this.zoom + this.zoomStep * (i ? 1 : -1); + this.zoom = min(this.zoomMax,max(this.zoomMin,z)); + this.Zoom(this.zoom); +}; + +TagCanvas.tc = {}; + +jQuery.fn.tagcanvas = function(options,lctr) { + var links, ctr = lctr ? jQuery('#'+lctr) : this; + if(doc.all && !lctr) return false; // IE must have external list + links = ctr.find('a'); + if(Defined(window.G_vmlCanvasManager)) { + this.each(function() { $(this)[0] = window.G_vmlCanvasManager.initElement($(this)[0]); }); + options.ie = parseFloat(navigator.appVersion.split('MSIE')[1]); + } + + if(!links.length || !this[0].getContext || !this[0].getContext('2d').fillText) + return false; + + this.each(function() { + var i, vl, im, ii, tag, jqt, w, weights = [], + pfuncs = { + sphere:PointsOnSphere, + vcylinder:PointsOnCylinderV, + hcylinder:PointsOnCylinderH + }; + + // if using internal links, get only the links for this canvas + lctr || (links = $(this).find('a')); + + jqt = new TagCanvas; + for(i in options) + jqt[i] = options[i]; + + jqt.z1 = (19800 / (Math.exp(jqt.depth) * (1-1/Math.E))) + + 20000 - 19800 / (1-(1/Math.E)); + jqt.z2 = jqt.z1 * (1/jqt.zoom); + + jqt.radius = (this.height > this.width ? this.width : this.height) + * 0.33 * (jqt.z2 + jqt.z1) / (jqt.z1); + //jqt.yaw = jqt.initial ? jqt.initial[0] * jqt.maxSpeed : 0; // mod Shad Laws + //jqt.pitch = jqt.initial ? jqt.initial[1] * jqt.maxSpeed : 0; // mod Shad Laws + jqt.yaw = jqt.initial && (jqt.lock != 'x') ? jqt.initial[0] * jqt.maxSpeed : 0; + jqt.pitch = jqt.initial && (jqt.lock != 'y') ? jqt.initial[1] * jqt.maxSpeed : 0; + jqt.canvas = $(this)[0]; + jqt.ctxt = jqt.canvas.getContext('2d'); + jqt.textFont = jqt.textFont && FixFont(jqt.textFont); + jqt.deadZone *= 1; // mod Shad Laws + jqt.minSpeed *= 1; // mod Shad Laws + jqt.decel *= 1; // mod Shad Laws + jqt.maxInputZone *= 1; // mod Shad Laws + jqt.pulsateTo *= 1; + jqt.textHeight *= 1; + jqt.minBrightness *= 1; + jqt.ctxt.textBaseline = 'top'; + if(jqt.shadowBlur || jqt.shadowOffset[0] || jqt.shadowOffset[1]) { + // let the browser translate "red" into "#ff0000" + jqt.ctxt.shadowColor = jqt.shadow; + jqt.shadow = jqt.ctxt.shadowColor; + jqt.shadowAlpha = ShadowAlphaBroken(); + } else { + delete jqt.shadow; + } + jqt.taglist = []; + + jqt.shape = pfuncs[jqt.shape] || pfuncs.sphere; + vl = jqt.shape(links.length, jqt.radiusX, jqt.radiusY, jqt.radiusZ); + jqt.shuffleTags && Shuffle(vl); + jqt.listLength = links.length; + for(i = 0; i < links.length; ++i) { + im = links[i].getElementsByTagName('img'); + if(im.length) { + ii = new Image; + ii.src = im[0].src; + tag = new Tag(jqt,ii, links[i], vl[i], 1, 1); + AddImage(ii,im[0],tag,jqt); + } else { + jqt.taglist.push(new Tag(jqt,links[i].innerText || links[i].textContent, links[i], + vl[i], 2, jqt.textHeight + 2, jqt.textColour || GetProperty(links[i],'color'), + jqt.textFont || FixFont(GetProperty(links[i],'font-family')))); + } + if(jqt.weight) { + w = FindWeight(jqt,links[i]); + if(w > jqt.max_weight) jqt.max_weight = w; + if(w < jqt.min_weight) jqt.min_weight = w; + weights.push(w); + } + } + if(jqt.weight = (jqt.max_weight > jqt.min_weight)) { + for(i = 0; i < jqt.taglist.length; ++i) { + jqt.taglist[i].SetWeight(weights[i]); + } + } + + TagCanvas.tc[$(this)[0].id] = jqt; + jqt.Tooltip = (jqt.tooltip == 'native' ? jqt.TooltipNative : (jqt.tooltip ? jqt.TooltipDiv : jqt.TooltipNone)); + if(jqt.tooltip) { + if(jqt.tooltip == 'native') { + jqt.Tooltip = jqt.TooltipNative; + } else { + jqt.Tooltip = jqt.TooltipDiv; + if(!jqt.ttdiv) { + jqt.ttdiv = doc.createElement('div'); + jqt.ttdiv.className = jqt.tooltipClass; + jqt.ttdiv.style.position = 'absolute'; + jqt.ttdiv.style.zIndex = jqt.canvas.style.zIndex + 1; + AddHandler('mouseover',function(e){e.target.style.display='none';},jqt.ttdiv); + doc.body.appendChild(jqt.ttdiv); + } + } + } else { + jqt.Tooltip = jqt.TooltipNone; + } + if(!jqt.noMouse && !handlers[$(this)[0].id]) { + // for some reason, using bind with mouseup isn't working in IE + AddHandler('mousemove', MouseMove, this); + AddHandler('mouseout', MouseOut, this); + AddHandler('mouseup', MouseClick, this); + if(jqt.wheelZoom) { + AddHandler('mousewheel', MouseWheel, this); + AddHandler('DOMMouseScroll', MouseWheel, this); + } + handlers[$(this)[0].id] = 1; + } + if(lctr && jqt.hideTags) { + if(TagCanvas.loaded) + $(ctr).hide(); + else + AddHandler('load', function() { $(ctr).hide() }, window); + } + options.interval = options.interval || jqt.interval; + }); + return !!(TagCanvas.started || (TagCanvas.started = setInterval(DrawCanvas, options.interval))); +}; +// set a flag for when the window has loaded +AddHandler('load',function(){TagCanvas.loaded=1;},window); +})(jQuery); diff --git a/modules/tag_cloud_html5/js/jquery.tagcanvas.mod.min.js b/modules/tag_cloud_html5/js/jquery.tagcanvas.mod.min.js new file mode 100644 index 0000000..b4f0191 --- /dev/null +++ b/modules/tag_cloud_html5/js/jquery.tagcanvas.mod.min.js @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2010-2012 Graham Breach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +/** + * jQuery.tagcanvas 1.17.1 + * For more information, please contact <graham@goat1000.com> + */ +/** + * Modified by Shad Laws 2012/06/08 -- all modified lines have "mod Shad Laws" comments + * - built a physics-based model for motion, which is activated with the physModel flag + * - included initialDecel, deadZone, maxInputZone, and physModel as options + * - set defaults of new options to mimic behavior without them (false, 0, 0, and false) + * - removed two unnecessary variable declarations caught by YUI Compressor + * - fixed initialization of a few variables (minSpeed, decel, yaw/pitch) + * - fixed problem with html margin-top changing coordinates in IE (but not Chrome or Firefox) + */ +(function(I){var S,R,G=Math.abs,r=Math.sin,h=Math.cos,z=Math.max,W=Math.min,M=Math.sqrt,B={},C={},D={0:"0,",1:"17,",2:"34,",3:"51,",4:"68,",5:"85,",6:"102,",7:"119,",8:"136,",9:"153,",a:"170,",A:"170,",b:"187,",B:"187,",c:"204,",C:"204,",d:"221,",D:"221,",e:"238,",E:"238,",f:"255,",F:"255,"},e,J,d,l=document,y,c={};for(S=0;S<256;++S){R=S.toString(16);if(S<16){R="0"+R}C[R]=C[R.toUpperCase()]=S.toString()+","}function O(i){return typeof(i)!="undefined"}function H(j){var Z=j.length-1,Y,aa;while(Z){aa=~~(Math.random()*Z);Y=j[Z];j[Z]=j[aa];j[aa]=Y;--Z}}function n(Z,ab,ag,ad){var ac,af,j,ae,ah=[],aa=Math.PI*(3-Math.sqrt(5)),Y=2/Z;for(ac=0;ac<Z;++ac){af=ac*Y-1+(Y/2);j=Math.sqrt(1-af*af);ae=ac*aa;ah.push([h(ae)*j*ab,af*ag,r(ae)*j*ad])}return ah}function V(aa,Y,ad,aj,ai,ag,af,ae,ac){var ah,ak=[],ab=Math.PI*(3-Math.sqrt(5)),Z=2/aa;for(ag=0;ag<aa;++ag){af=ag*Z-1+(Z/2);ah=ag*ab;ae=h(ah);ac=r(ah);ak.push(Y?[af*ad,ae*aj,ac*ai]:[ae*ad,af*aj,ac*ai])}return ak}function w(Z,i,j,Y){return V(Z,0,i,j,Y)}function F(Z,i,j,Y){return V(Z,1,i,j,Y)}function p(ab,i){var aa=ab,Z,Y,j=(i*1).toPrecision(3)+")";if(ab[0]==="#"){if(!B[ab]){if(ab.length===4){B[ab]="rgba("+D[ab[1]]+D[ab[2]]+D[ab[3]]}else{B[ab]="rgba("+C[ab.substr(1,2)]+C[ab.substr(3,2)]+C[ab.substr(5,2)]}}aa=B[ab]+j}else{if(ab.substr(0,4)==="rgb("||ab.substr(0,4)==="hsl("){aa=(ab.replace("(","a(").replace(")",","+j))}else{if(ab.substr(0,5)==="rgba("||ab.substr(0,5)==="hsla("){Z=ab.lastIndexOf(",")+1,Y=ab.indexOf(")");i*=parseFloat(ab.substring(Z,Y));aa=ab.substr(0,Z)+i.toPrecision(3)+")"}}}return aa}function g(i,j){if(window.G_vmlCanvasManager){return null}var Y=l.createElement("canvas");Y.width=i;Y.height=j;return Y}function v(){var j=g(3,3),Z,Y;if(!j){return false}Z=j.getContext("2d");Z.strokeStyle="#000";Z.shadowColor="#fff";Z.shadowBlur=3;Z.globalAlpha=0;Z.strokeRect(2,2,2,2);Z.globalAlpha=1;Y=Z.getImageData(2,2,1,1);j=null;return(Y.data[0]>0)}function X(af,j){var Y=1024,ab=af.weightGradient,aa,ad,Z,ae,ac;if(af.gCanvas){ad=af.gCanvas.getContext("2d")}else{af.gCanvas=aa=g(Y,1);if(!aa){return null}ad=aa.getContext("2d");ae=ad.createLinearGradient(0,0,Y,0);for(Z in ab){ae.addColorStop(1-Z,ab[Z])}ad.fillStyle=ae;ad.fillRect(0,0,Y,1)}ac=ad.getImageData(~~((Y-1)*j),0,1,1).data;return"rgba("+ac[0]+","+ac[1]+","+ac[2]+","+(ac[3]/255)+")"}function u(ab,aa,Y,ae,ac,ad,j){var Z=(ad||0)+(j&&j[0]<0?G(j[0]):0),i=(ad||0)+(j&&j[1]<0?G(j[1]):0);ab.font=aa;ab.textBaseline="top";ab.fillStyle=Y;ac&&(ab.shadowColor=ac);ad&&(ab.shadowBlur=ad);j&&(ab.shadowOffsetX=j[0],ab.shadowOffsetY=j[1]);ab.fillText(ae,Z,i)}function m(ak,ac,ag,ai,ab,Y,ae,af,j,aj,ah){var Z=ai+G(j[0])+af+af,i=ab+G(j[1])+af+af,aa,ad;aa=g(Z+aj,i+ah);if(!aa){return null}ad=aa.getContext("2d");u(ad,ac,Y,ak,ae,af,j);return aa}function Q(ac,af,ag,Z){var aa=ac.width+G(Z[0])+ag+ag,j=ac.height+G(Z[1])+ag+ag,ad,ae,ab=(ag||0)+(Z&&Z[0]<0?G(Z[0]):0),Y=(ag||0)+(Z&&Z[1]<0?G(Z[1]):0);ad=g(aa,j);if(!ad){return null}ae=ad.getContext("2d");af&&(ae.shadowColor=af);ag&&(ae.shadowBlur=ag);Z&&(ae.shadowOffsetX=Z[0],ae.shadowOffsetY=Z[1]);ae.drawImage(ac,ab,Y);return ad}function K(ak,ac,ai){var aj=parseInt(ak.length*ai),ab=parseInt(ai*2),Z=g(aj,ab),af,j,aa,ae,ah,ag,Y,ad;if(!Z){return null}af=Z.getContext("2d");af.fillStyle="#000";af.fillRect(0,0,aj,ab);u(af,ai+"px "+ac,"#fff",ak);j=af.getImageData(0,0,aj,ab);aa=j.width;ae=j.height;ad={min:{x:aa,y:ae},max:{x:-1,y:-1}};for(ag=0;ag<ae;++ag){for(ah=0;ah<aa;++ah){Y=(ag*aa+ah)*4;if(j.data[Y+1]>0){if(ah<ad.min.x){ad.min.x=ah}if(ah>ad.max.x){ad.max.x=ah}if(ag<ad.min.y){ad.min.y=ag}if(ag>ad.max.y){ad.max.y=ag}}}}if(aa!=aj){ad.min.x*=(aj/aa);ad.max.x*=(aj/aa)}if(ae!=ab){ad.min.y*=(aj/ae);ad.max.y*=(aj/ae)}Z=null;return ad}function t(i){return"'"+i.replace(/(\'|\")/g,"").replace(/\s*,\s*/g,"', '")+"'"}function A(i,j,Y){Y=Y||l;if(Y.addEventListener){Y.addEventListener(i,j,false)}else{Y.attachEvent("on"+i,j)}}function P(aa,ac,Z,j){var Y=j.taglist,ab=j.imageScale;if(ab&&!(ac.width&&ac.height)){A("load",function(){P(aa,ac,Z,j)},window);return}if(!aa.complete){A("load",function(){P(aa,ac,Z,j)},aa);return}ac.width=ac.width;ac.height=ac.height;if(ab){aa.width=ac.width*ab;aa.height=ac.height*ab}Z.w=aa.width;Z.h=aa.height;Y.push(Z)}function N(Z,Y){var j=l.defaultView,i=Y.replace(/\-([a-z])/g,function(aa){return aa.charAt(1).toUpperCase()});return(j&&j.getComputedStyle&&j.getComputedStyle(Z,null).getPropertyValue(Y))||(Z.currentStyle&&Z.currentStyle[i])}function x(Y,j){var i=1,Z;if(Y.weightFrom){i=1*(j.getAttribute(Y.weightFrom)||Y.textHeight)}else{if(Z=N(j,"font-size")){i=(Z.indexOf("px")>-1&&Z.replace("px","")*1)||(Z.indexOf("pt")>-1&&Z.replace("pt","")*1.25)||Z*3.3}else{Y.weight=false}}return i}function k(Y){L(Y);var j=Y.target||Y.fromElement.parentNode,i=s.tc[j.id];i&&(i.mx=i.my=-1)}function L(aa){var Z,Y,j=l.documentElement,ab;for(Z in s.tc){Y=s.tc[Z];if(Y.tttimer){clearTimeout(Y.tttimer);Y.tttimer=null}ab=I(Y.canvas).offset();if(aa.offsetX){Y.mx=aa.offsetX;Y.my=aa.offsetY}else{if(aa.pageX){Y.mx=aa.pageX-ab.left;Y.my=aa.pageY-ab.top}else{Y.mx=aa.clientX+(j.scrollLeft||l.body.scrollLeft)-ab.left;Y.my=aa.clientY+(j.scrollTop||l.body.scrollTop)-ab.top}}}}function q(Z){var j=s,i=l.addEventListener?0:1,Y=Z.target&&O(Z.target.id)?Z.target.id:Z.srcElement.parentNode.id;if(Y&&Z.button==i&&j.tc[Y]){L(Z);j.tc[Y].Clicked(Z)}}function U(Y){var i=s,j=Y.target&&O(Y.target.id)?Y.target.id:Y.srcElement.parentNode.id;if(j&&i.tc[j]){Y.cancelBubble=true;Y.returnValue=false;Y.preventDefault&&Y.preventDefault();i.tc[j].Wheel((Y.wheelDelta||Y.detail)>0)}}function o(){var Y=s.tc,j;for(j in Y){Y[j].Draw()}}function b(Y,i){var j=r(i),Z=h(i);return{x:Y.x,y:(Y.y*Z)+(Y.z*j),z:(Y.y*-j)+(Y.z*Z)}}function a(Y,i){var j=r(i),Z=h(i);return{x:(Y.x*Z)+(Y.z*-j),y:Y.y,z:(Y.x*j)+(Y.z*Z)}}function T(Y,af,ae,aa,ad,ab){var i,Z,ac,j=Y.z1/(Y.z1+Y.z2+af.z);i=af.y*j*ab;Z=af.x*j*ad;ac=Y.z2+af.z;return{x:Z,y:i,z:ac}}function f(i){this.ts=new Date().valueOf();this.tc=i;this.x=this.y=this.w=this.h=this.sc=1;this.z=0;this.Draw=i.pulsateTo<1&&i.outlineMethod!="colour"?this.DrawPulsate:this.DrawSimple;this.SetMethod(i.outlineMethod)}e=f.prototype;e.SetMethod=function(Y){var j={block:["PreDraw","DrawBlock"],colour:["PreDraw","DrawColour"],outline:["PostDraw","DrawOutline"],classic:["LastDraw","DrawOutline"],none:["LastDraw"]},i=j[Y]||j.outline;if(Y=="none"){this.Draw=function(){return 1}}else{this.drawFunc=this[i[1]]}this[i[0]]=this.Draw};e.Update=function(ae,ad,af,ab,ac,j,aa,i){var Y=this.tc.outlineOffset,Z=2*Y;this.x=ac*ae+aa-Y;this.y=ac*ad+i-Y;this.w=ac*af+Z;this.h=ac*ab+Z;this.sc=ac;this.z=j.z};e.DrawOutline=function(ab,i,aa,j,Y,Z){ab.strokeStyle=Z;ab.strokeRect(i,aa,j,Y)};e.DrawColour=function(Z,ac,aa,ad,Y,i,ae,j,ab){return this[ae.image?"DrawColourImage":"DrawColourText"](Z,ac,aa,ad,Y,i,ae,j,ab)};e.DrawColourText=function(aa,ad,ab,ae,Y,i,af,j,ac){var Z=af.colour;af.colour=i;af.Draw(aa,j,ac);af.colour=Z;return 1};e.DrawColourImage=function(ad,ag,ae,ah,ac,i,ak,j,af){var ai=ad.canvas,aa=~~z(ag,0),Z=~~z(ae,0),ab=W(ai.width-aa,ah)+0.5|0,aj=W(ai.height-Z,ac)+0.5|0,Y;if(y){y.width=ab,y.height=aj}else{y=g(ab,aj)}if(!y){return this.SetMethod("outline")}Y=y.getContext("2d");Y.drawImage(ai,aa,Z,ab,aj,0,0,ab,aj);ad.clearRect(aa,Z,ab,aj);ak.Draw(ad,j,af);ad.setTransform(1,0,0,1,0,0);ad.save();ad.beginPath();ad.rect(aa,Z,ab,aj);ad.clip();ad.globalCompositeOperation="source-in";ad.fillStyle=i;ad.fillRect(aa,Z,ab,aj);ad.restore();ad.globalCompositeOperation="destination-over";ad.drawImage(y,0,0,ab,aj,aa,Z,ab,aj);ad.globalCompositeOperation="source-over";return 1};e.DrawBlock=function(ab,i,aa,j,Y,Z){ab.fillStyle=Z;ab.fillRect(i,aa,j,Y)};e.DrawSimple=function(aa,i,j,Z){var Y=this.tc;aa.setTransform(1,0,0,1,0,0);aa.strokeStyle=Y.outlineColour;aa.lineWidth=Y.outlineThickness;aa.shadowBlur=aa.shadowOffsetX=aa.shadowOffsetY=0;aa.globalAlpha=1;return this.drawFunc(aa,this.x,this.y,this.w,this.h,Y.outlineColour,i,j,Z)};e.DrawPulsate=function(ab,i,j,Z){var aa=new Date().valueOf()-this.ts,Y=this.tc;ab.setTransform(1,0,0,1,0,0);ab.strokeStyle=Y.outlineColour;ab.lineWidth=Y.outlineThickness;ab.shadowBlur=ab.shadowOffsetX=ab.shadowOffsetY=0;ab.globalAlpha=Y.pulsateTo+((1-Y.pulsateTo)*(0.5+(h(2*Math.PI*aa/(1000*Y.pulsateTime))/2)));return this.drawFunc(ab,this.x,this.y,this.w,this.h,Y.outlineColour,i,j,Z)};e.Active=function(Y,i,j){return(i>=this.x&&j>=this.y&&i<=this.x+this.w&&j<=this.y+this.h)};e.PreDraw=e.PostDraw=e.LastDraw=function(){};function E(aa,j,ae,ag,af,ac,Y,Z){var ad=aa.ctxt,ab;this.tc=aa;this.image=j.src?j:null;this.name=j.src?"":j;this.title=ae.title||null;this.a=ae;this.p3d={x:ag[0]*aa.radius*1.1,y:ag[1]*aa.radius*1.1,z:ag[2]*aa.radius*1.1};this.x=this.y=0;this.w=af;this.h=ac;this.colour=Y||aa.textColour;this.textFont=Z||aa.textFont;this.weight=this.sc=this.alpha=1;this.weighted=!aa.weight;this.outline=new f(aa);if(this.image){if(aa.txtOpt&&aa.shadow){ab=Q(this.image,aa.shadow,aa.shadowBlur,aa.shadowOffset);if(ab){this.image=ab;this.w=ab.width;this.h=ab.height}}}else{this.textHeight=aa.textHeight;this.extents=K(this.name,this.textFont,this.textHeight);this.Measure(ad,aa)}this.SetShadowColour=aa.shadowAlpha?this.SetShadowColourAlpha:this.SetShadowColourFixed;this.SetDraw(aa)}J=E.prototype;J.SetDraw=function(i){this.Draw=this.image?(i.ie>7?this.DrawImageIE:this.DrawImage):this.DrawText;i.noSelect&&(this.CheckActive=function(){})};J.Measure=function(ac,j){this.h=this.extents?this.extents.max.y+this.extents.min.y:this.textHeight;ac.font=this.font=this.textHeight+"px "+this.textFont;this.w=ac.measureText(this.name).width;if(j.txtOpt){var Z=j.txtScale,aa=Z*this.textHeight,ab=aa+"px "+this.textFont,Y=[Z*j.shadowOffset[0],Z*j.shadowOffset[1]],i;ac.font=ab;i=ac.measureText(this.name).width;this.image=m(this.name,ab,aa,i,Z*this.h,this.colour,j.shadow,Z*j.shadowBlur,Y,Z,Z);if(this.image){this.w=this.image.width/Z;this.h=this.image.height/Z}this.SetDraw(j);j.txtOpt=this.image}};J.SetWeight=function(i){if(!this.name.length){return}this.weight=i;this.Weight(this.tc.ctxt,this.tc);this.Measure(this.tc.ctxt,this.tc)};J.Weight=function(Z,Y){var j=this.weight,i=Y.weightMode;this.weighted=true;if(i=="colour"||i=="both"){this.colour=X(Y,(j-Y.min_weight)/(Y.max_weight-Y.min_weight))}if(i=="size"||i=="both"){this.textHeight=j*Y.weightSize}this.extents=K(this.name,this.textFont,this.textHeight)};J.SetShadowColourFixed=function(Y,j,i){Y.shadowColor=j};J.SetShadowColourAlpha=function(Y,j,i){Y.shadowColor=p(j,i)};J.DrawText=function(Y,ac,j){var ad=this.tc,aa=this.x,Z=this.y,ab,i,ae=this.sc;Y.globalAlpha=this.alpha;Y.setTransform(ae,0,0,ae,0,0);Y.fillStyle=this.colour;ad.shadow&&this.SetShadowColour(Y,ad.shadow,this.alpha);Y.font=this.font;ab=this.w;i=this.h;aa+=(ac/ae)-(ab/2);Z+=(j/ae)-(i/2);Y.fillText(this.name,aa,Z)};J.DrawImage=function(aa,ag,Z){var ad=this.x,ab=this.y,ah=this.sc,j=this.image,ae=this.w,Y=this.h,ac=this.alpha,af=this.shadow;aa.globalAlpha=ac;aa.setTransform(ah,0,0,ah,0,0);aa.fillStyle=this.colour;af&&this.SetShadowColour(aa,af,ac);ad+=(ag/ah)-(ae/2);ab+=(Z/ah)-(Y/2);aa.drawImage(j,ad,ab,ae,Y)};J.DrawImageIE=function(aa,ae,Z){var j=this.image,af=this.sc,ad=j.width=this.w*af,Y=j.height=this.h*af,ac=(this.x*af)+ae-(ad/2),ab=(this.y*af)+Z-(Y/2);aa.setTransform(1,0,0,1,0,0);aa.globalAlpha=this.alpha;aa.drawImage(j,ac,ab)};J.Calc=function(aa,Z){var i=a(this.p3d,aa),j=this.tc,ab=j.minBrightness,Y=j.radius;this.p3d=b(i,Z);i=T(j,this.p3d,this.w,this.h,j.stretchX,j.stretchY);this.x=i.x;this.y=i.y;this.sc=(j.z1+j.z2-i.z)/j.z2;this.alpha=z(ab,W(1,ab+1-((i.z-j.z2+Y)/(2*Y))))};J.CheckActive=function(Z,ad,Y){var ae=this.tc,i=this.outline,ac=this.w,j=this.h,ab=this.x-ac/2,aa=this.y-j/2;i.Update(ab,aa,ac,j,this.sc,this.p3d,ad,Y);return i.Active(Z,ae.mx,ae.my)?i:null};J.Clicked=function(ab){var j=this.a,Y=j.target,Z=j.href,i;if(Y!=""&&Y!="_self"){if(self.frames[Y]){self.frames[Y]=Z}else{try{if(top.frames[Y]){top.frames[Y]=Z;return}}catch(aa){}window.open(Z,Y)}return}if(l.createEvent){i=l.createEvent("MouseEvents");i.initMouseEvent("click",1,1,window,0,0,0,0,0,0,0,0,0,0,null);if(!j.dispatchEvent(i)){return}}else{if(j.fireEvent){if(!j.fireEvent("onclick")){return}}}l.location=Z};function s(){var j,Y={mx:-1,my:-1,z1:20000,z2:20000,z0:0.0002,freezeActive:false,activeCursor:"pointer",pulsateTo:1,pulsateTime:3,reverse:false,depth:0.5,maxSpeed:0.05,minSpeed:0,decel:0.95,interval:20,initial:null,initialDecel:false,deadZone:0,physModel:false,maxInputZone:0,hideTags:true,minBrightness:0.1,outlineColour:"#ffff99",outlineThickness:2,outlineOffset:5,outlineMethod:"outline",textColour:"#ff99ff",textHeight:15,textFont:"Helvetica, Arial, sans-serif",shadow:"#000",shadowBlur:0,shadowOffset:[0,0],zoom:1,weight:false,weightMode:"size",weightFrom:null,weightSize:1,weightGradient:{0:"#f00",0.33:"#ff0",0.66:"#0f0",1:"#00f"},txtOpt:true,txtScale:2,frontSelect:false,wheelZoom:true,zoomMin:0.3,zoomMax:3,zoomStep:0.05,shape:"sphere",lock:null,tooltip:null,tooltipDelay:300,tooltipClass:"tctooltip",radiusX:1,radiusY:1,radiusZ:1,stretchX:1,stretchY:1,shuffleTags:false,noSelect:false,noMouse:false,imageScale:1};for(j in Y){this[j]=Y[j]}this.max_weight=0;this.min_weight=200}d=s.prototype;d.Draw=function(){var ah=this.canvas,af=ah.width,Y=ah.height,j=0,ae=this.yaw,Z=this.pitch,aa=af/2,ak=Y/2,ai=this.ctxt,ac,aj,ag,ad=-1,am=this.taglist,ab=am.length,al=this.frontSelect;if(ae==0&&Z==0&&this.drawn&&!this.zoneActive){return this.Animate(af,Y)}ai.setTransform(1,0,0,1,0,0);this.active=null;for(ag=0;ag<ab;++ag){am[ag].Calc(ae,Z)}am=am.sort(function(an,i){return an.sc-i.sc});for(ag=0;ag<ab;++ag){aj=am[ag].CheckActive(ai,aa,ak);aj=this.mx>=0&&this.my>=0&&am[ag].CheckActive(ai,aa,ak);if(aj&&aj.sc>j&&(!al||aj.z<=0)){ac=aj;ac.index=ad=ag;j=aj.sc}}this.active=ac;if(!this.txtOpt&&this.shadow){ai.shadowBlur=this.shadowBlur;ai.shadowOffsetX=this.shadowOffset[0];ai.shadowOffsetY=this.shadowOffset[1]}ai.clearRect(0,0,af,Y);for(ag=0;ag<ab;++ag){if(!(ad==ag&&ac.PreDraw(ai,am[ag],aa,ak))){am[ag].Draw(ai,aa,ak)}ad==ag&&ac.PostDraw(ai)}if(this.freezeActive&&ac){this.yaw=this.pitch=this.drawn=0}else{this.Animate(af,Y);this.drawn=(ab==this.listLength)}ac&&ac.LastDraw(ai);ah.style.cursor=ac?this.activeCursor:"";this.Tooltip(ac,am[ad])};d.TooltipNone=function(){};d.TooltipNative=function(j,i){this.canvas.title=j&&i.title?i.title:""};d.TooltipDiv=function(Z,j){var i=this,Y=i.ttdiv.style;if(Z&&j.title){i.ttdiv.innerHTML=j.title;if(Y.display=="none"&&!i.tttimer){i.tttimer=setTimeout(function(){var aa=I(i.canvas).offset();Y.display="block";Y.left=aa.left+i.mx+"px";Y.top=aa.top+i.my+24+"px";i.tttimer=null},i.tooltipDelay)}}else{Y.display="none"}};d.Animate=function(ae,aw){var Z=this;if(Z.physModel){var ac=Z.mx,aa=Z.my,aq=ae-1,ax=aw-1,ai=Z.stretchX,av=Z.stretchY;var aj=Z.reverse?-1:1,au=Z.lock,al=Z.maxInputZone,i=Z.z0;var aA=1-Z.decel,az=Z.deadZone,at=Z.maxSpeed*aA,ar=Z.minSpeed*aA;var aC=Z.pitch,aB=Z.yaw,ae=M(aC*aC+aB*aB),ao,aD,ad;var Y,j,ag,am,ak,af;this.zoneActive=false;if(ac>=0&&aa>=0&&ac<=aq&&aa<=ax){Y=-aj*(2*aa/ax-1)*z(ax/aq,1)/av;j=aj*(2*ac/aq-1)*z(aq/ax,1)/ai;if(G(Y)<=1+al&&G(j)<=1+al){Y=(au!="y")?Y:0;j=(au!="x")?j:0;ag=M(Y*Y+j*j);if(ag<=az||az>=1-i){this.initial=null;this.zoneActive=true;Y=0,j=0,ag=0;am=0,ak=0,af=0}else{if(ag<=1){this.initial=null;this.zoneActive=true;Y=Y/ag*(ag-az)/(1-az),j=j/ag*(ag-az)/(1-az),ag=(ag-az)/(1-az);am=0,ak=0,af=0}else{if(ag<=1+al){this.initial=null;this.zoneActive=true;Y=Y/ag,j=j/ag,ag=1;am=0,ak=0,af=0}}}}}if(!this.zoneActive){Y=0,j=0,ag=0;if(ae>=i){am=aC/ae,ak=aB/ae,af=1}else{af=2*Math.PI*Math.random();if(au=="x"){am=0,ak=af<Math.PI?1:-1,af=1}else{if(au=="y"){am=af<Math.PI?1:-1,ak=0,af=1}else{am=h(af),ak=r(af),af=1}}}}if(!Z.initial||Z.initialDecel){ao=aC-aA*aC+at*Y+ar*am;aD=aB-aA*aB+at*j+ar*ak;ad=M(ao*ao+aD*aD);if(ad<i){ao=0,aD=0,ad=0}this.pitch=ao;this.yaw=aD}}else{var ac=Z.mx,aa=Z.my,au=Z.lock,ah,ab,an,aj;Z.zoneActive=false;if(ac>=0&&aa>=0&&ac<ae&&aa<aw){Z.zoneActive=true;ah=Z.maxSpeed,aj=Z.reverse?-1:1,dz=Z.deadZone;if(au!="x"){this.yaw=aj*ah/z(1-dz,0.000001)*(z(2*ac/ae-1-dz,0)+W(2*ac/ae-1+dz,0))}if(au!="y"){this.pitch=-aj*ah/z(1-dz,0.000001)*(z(2*aa/aw-1-dz,0)+W(2*aa/aw-1+dz,0))}this.initial=null}else{if(!Z.initial||Z.initialDecel){ah=Z.minSpeed,ab=G(Z.yaw),an=G(Z.pitch);if(au!="x"&&ab>ah){this.yaw=ab>Z.z0?Z.yaw*Z.decel:0}if(au!="y"&&an>ah){this.pitch=an>Z.z0?Z.pitch*Z.decel:0}}}}};d.Zoom=function(i){this.z2=this.z1*(1/i);this.drawn=0};d.Clicked=function(Z){var Y=this.taglist,i=this.active;try{if(i&&Y[i.index]){Y[i.index].Clicked(Z)}}catch(j){}};d.Wheel=function(j){var Y=this.zoom+this.zoomStep*(j?1:-1);this.zoom=W(this.zoomMax,z(this.zoomMin,Y));this.Zoom(this.zoom)};s.tc={};jQuery.fn.tagcanvas=function(Y,j){var i,Z=j?jQuery("#"+j):this;if(l.all&&!j){return false}i=Z.find("a");if(O(window.G_vmlCanvasManager)){this.each(function(){I(this)[0]=window.G_vmlCanvasManager.initElement(I(this)[0])});Y.ie=parseFloat(navigator.appVersion.split("MSIE")[1])}if(!i.length||!this[0].getContext||!this[0].getContext("2d").fillText){return false}this.each(function(){var ac,aa,ae,ah,ai,ad,ag,af=[],ab={sphere:n,vcylinder:w,hcylinder:F};j||(i=I(this).find("a"));ad=new s;for(ac in Y){ad[ac]=Y[ac]}ad.z1=(19800/(Math.exp(ad.depth)*(1-1/Math.E)))+20000-19800/(1-(1/Math.E));ad.z2=ad.z1*(1/ad.zoom);ad.radius=(this.height>this.width?this.width:this.height)*0.33*(ad.z2+ad.z1)/(ad.z1);ad.yaw=ad.initial&&(ad.lock!="x")?ad.initial[0]*ad.maxSpeed:0;ad.pitch=ad.initial&&(ad.lock!="y")?ad.initial[1]*ad.maxSpeed:0;ad.canvas=I(this)[0];ad.ctxt=ad.canvas.getContext("2d");ad.textFont=ad.textFont&&t(ad.textFont);ad.deadZone*=1;ad.minSpeed*=1;ad.decel*=1;ad.maxInputZone*=1;ad.pulsateTo*=1;ad.textHeight*=1;ad.minBrightness*=1;ad.ctxt.textBaseline="top";if(ad.shadowBlur||ad.shadowOffset[0]||ad.shadowOffset[1]){ad.ctxt.shadowColor=ad.shadow;ad.shadow=ad.ctxt.shadowColor;ad.shadowAlpha=v()}else{delete ad.shadow}ad.taglist=[];ad.shape=ab[ad.shape]||ab.sphere;aa=ad.shape(i.length,ad.radiusX,ad.radiusY,ad.radiusZ);ad.shuffleTags&&H(aa);ad.listLength=i.length;for(ac=0;ac<i.length;++ac){ae=i[ac].getElementsByTagName("img");if(ae.length){ah=new Image;ah.src=ae[0].src;ai=new E(ad,ah,i[ac],aa[ac],1,1);P(ah,ae[0],ai,ad)}else{ad.taglist.push(new E(ad,i[ac].innerText||i[ac].textContent,i[ac],aa[ac],2,ad.textHeight+2,ad.textColour||N(i[ac],"color"),ad.textFont||t(N(i[ac],"font-family"))))}if(ad.weight){ag=x(ad,i[ac]);if(ag>ad.max_weight){ad.max_weight=ag}if(ag<ad.min_weight){ad.min_weight=ag}af.push(ag)}}if(ad.weight=(ad.max_weight>ad.min_weight)){for(ac=0;ac<ad.taglist.length;++ac){ad.taglist[ac].SetWeight(af[ac])}}s.tc[I(this)[0].id]=ad;ad.Tooltip=(ad.tooltip=="native"?ad.TooltipNative:(ad.tooltip?ad.TooltipDiv:ad.TooltipNone));if(ad.tooltip){if(ad.tooltip=="native"){ad.Tooltip=ad.TooltipNative}else{ad.Tooltip=ad.TooltipDiv;if(!ad.ttdiv){ad.ttdiv=l.createElement("div");ad.ttdiv.className=ad.tooltipClass;ad.ttdiv.style.position="absolute";ad.ttdiv.style.zIndex=ad.canvas.style.zIndex+1;A("mouseover",function(aj){aj.target.style.display="none"},ad.ttdiv);l.body.appendChild(ad.ttdiv)}}}else{ad.Tooltip=ad.TooltipNone}if(!ad.noMouse&&!c[I(this)[0].id]){A("mousemove",L,this);A("mouseout",k,this);A("mouseup",q,this);if(ad.wheelZoom){A("mousewheel",U,this);A("DOMMouseScroll",U,this)}c[I(this)[0].id]=1}if(j&&ad.hideTags){if(s.loaded){I(Z).hide()}else{A("load",function(){I(Z).hide()},window)}}Y.interval=Y.interval||ad.interval});return !!(s.started||(s.started=setInterval(o,Y.interval)))};A("load",function(){s.loaded=1},window)})(jQuery);
\ No newline at end of file diff --git a/modules/tag_cloud_html5/libraries/MY_Form_Input.php b/modules/tag_cloud_html5/libraries/MY_Form_Input.php new file mode 100644 index 0000000..c191dcc --- /dev/null +++ b/modules/tag_cloud_html5/libraries/MY_Form_Input.php @@ -0,0 +1,58 @@ +<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 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 Form_Input extends Form_Input_Core {
+
+ /**
+ * Custom validation rule: numrange
+ * 0 args : returns error if not numeric
+ * 1 arg : returns error if not numeric OR if below min
+ * 2 args : returns error if not numeric OR if below min OR if above max
+ */
+ protected function rule_numrange($min = null, $max = null) {
+ if (is_numeric($this->value)) {
+ if (!is_null($min) && ($this->value < $min)) {
+ // below min
+ $this->errors['numrange'] = true;
+ $this->error_messages['numrange'] = t('Value is below minimum of').' '.$min;
+ } elseif (!is_null($max) && ($this->value > $max)) {
+ // above max
+ $this->errors['numrange'] = true;
+ $this->error_messages['numrange'] = t('Value is above maximum of').' '.$max;;
+ }
+ } else {
+ // not numeric
+ $this->errors['numrange'] = true;
+ $this->error_messages['numrange'] = t('Value is not numeric');
+ }
+ }
+
+ /**
+ * Custom validation rule: color
+ * returns no error if string is formatted as #hhhhhh OR if string is empty
+ * to exclude the empty case, add "required" as another rule
+ */
+ protected function rule_color() {
+ if (preg_match("/^#[0-9A-Fa-f]{6}$|^$/", $this->value) == 0) {
+ $this->errors['color'] = true;
+ $this->error_messages['color'] = t('Color is not in #hhhhhh format');
+ }
+ }
+}
\ No newline at end of file diff --git a/modules/tag_cloud_html5/module.info b/modules/tag_cloud_html5/module.info new file mode 100644 index 0000000..e158d15 --- /dev/null +++ b/modules/tag_cloud_html5/module.info @@ -0,0 +1,7 @@ +name = "Tag Cloud HTML5" +description = "HTML5-compliant tag cloud. Functions as non-Flash replacements for both 'tag_cloud' and 'tag_cloud_page' modules, with some extra features added." +version = 7 +author_name = "Shad Laws" +author_url = "" +info_url = "http://codex.gallery2.org/Gallery3:Modules:tag_cloud_html5" +discuss_url = "http://gallery.menalto.com/node/106774"
\ No newline at end of file diff --git a/modules/tag_cloud_html5/views/admin_tag_cloud_html5.html.php b/modules/tag_cloud_html5/views/admin_tag_cloud_html5.html.php new file mode 100644 index 0000000..026624a --- /dev/null +++ b/modules/tag_cloud_html5/views/admin_tag_cloud_html5.html.php @@ -0,0 +1,29 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<style> + @import "<?= url::file("modules/tag_cloud_html5/css/admin_tag_cloud_html5.css"); ?>"; +</style> +<div id="g-tag-cloud-html5-admin"> + <h2> + <?= t("Tag cloud HTML5 settings") ?> + </h2> + <p> + <b><?= t("Underlying JS libraries:") ?></b><br/> + <?= "1. <a href='http://www.goat1000.com/tagcanvas.php'>TagCanvas</a>: ".t("a non-flash, HTML5-compliant jQuery plugin.") ?><br/> + <?= "2. <a href='http://excanvas.sourceforge.net'>excanvas</a>: ".t("maintains canvas compatibility with pre-9.0 Internet Explorer, and does not load if not needed.") ?><br/> + <?= t("The module author, Shad Laws, has modified TagCanvas to add a physics-based model for motion and some extra parameters.") ?> + <?= t("Although this module loads a minified version of the JS library, the full-sized one is included in the JS directory for reference.") ?> + </p> + <p> + <b><?= t("How sizing works in TagCanvas:") ?></b><br/> + <?= "1. ".t("make a square the size of the minimum of width and height (as determined by width and height parameters)") ?><br/> + <?= "2. ".t("scale result by the stretch factors, possibility resulting in a non-square shape") ?><br/> + <?= "3. ".t("set text into result at defined text height") ?><br/> + <?= "4. ".t("scale result by the zoom, scaling both cloud and text height (e.g. text height 12 and zoom 1.25 results in 15pt font)") ?> + </p> + <p> + <b><?= t("Legend:") ?></b><br/> + <?= t(" option name (more info on option) {related TagCanvas parameters}") ?><br/> + <?= t("More details on TagCanvas parameters given at TagCanvas's homepage or in the above-mentioned JS library.") ?> + </p> + <?= $form ?> +</div> diff --git a/modules/tag_cloud_html5/views/tag_cloud_html5_block.html.php b/modules/tag_cloud_html5/views/tag_cloud_html5_block.html.php new file mode 100644 index 0000000..80360fe --- /dev/null +++ b/modules/tag_cloud_html5/views/tag_cloud_html5_block.html.php @@ -0,0 +1,40 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<!--[if lt IE 9]> +<?= html::script(gallery::find_file("js", "excanvas.compiled.js", false)) ?> +<![endif]--> +<script type="text/javascript"> + $(document).ready(function() { + + // set g-tag-cloud-html5-canvas size + $("#g-tag-cloud-html5-canvas").attr({ + 'width' : Math.floor($("#g-tag-cloud-html5").width()*<?= $width ?>), + 'height': Math.floor($("#g-tag-cloud-html5").width()*<?= $height ?>) + }); + + // start g-tag-cloud-html5-canvas + if(!$('#g-tag-cloud-html5-canvas').tagcanvas(<?= $options ?>,'g-tag-cloud-html5-tags')) { + // something went wrong, hide the canvas container g-tag-cloud-html5 + $('#g-tag-cloud-html5').hide(); + }; + + // tag autocomplete for g-add-tag-form + $("#g-add-tag-form input:text").autocomplete( + "<?= url::site("/tags/autocomplete") ?>", { + max: 30, + multiple: true, + multipleSeparator: ',', + cacheLength: 1} + ); + + }); +</script> +<div id="g-tag-cloud-html5"> + <canvas id="g-tag-cloud-html5-canvas"> + <? echo t('Tag cloud loading...'); ?> + </canvas> +</div> +<div id="g-tag-cloud-html5-tags"> + <?= $cloud ?> +</div> +<?= $wholecloud_link ?> +<?= $form ?> diff --git a/modules/tag_cloud_html5/views/tag_cloud_html5_embed.html.php b/modules/tag_cloud_html5/views/tag_cloud_html5_embed.html.php new file mode 100644 index 0000000..99e5af4 --- /dev/null +++ b/modules/tag_cloud_html5/views/tag_cloud_html5_embed.html.php @@ -0,0 +1,59 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<!--[if lt IE 9]> +<?= html::script(gallery::find_file("js", "excanvas.compiled.js", false)) ?> +<![endif]--> +<script type="text/javascript"> + // define flag variables if not already defined elsewhere + if (typeof(jQueryScriptFlag) == 'undefined') { + var jQueryScriptFlag = false; + }; + if (typeof(jQueryTagCanvasScriptFlag) == 'undefined') { + var jQueryTagCanvasScriptFlag = false; + }; + function initScripts() { + // load scripts if not already loaded + if (typeof(jQuery) == 'undefined') { + if (!jQueryScriptFlag) { + // load both scripts + jQueryScriptFlag = true; + jQueryTagCanvasScriptFlag = true; + document.write("<scr" + "ipt type=\"text/javascript\" src=\"<?= url::base(false).gallery::find_file("js", "jquery.js", false) ?>\"></scr" + "ipt>"); + document.write("<scr" + "ipt type=\"text/javascript\" src=\"<?= url::base(false).gallery::find_file("js", "jquery.tagcanvas.mod.min.js", false) ?>\"></scr" + "ipt>"); + }; + setTimeout("initScripts()", 50); + } else if (typeof(jQuery().tagcanvas) == 'undefined') { + if (!jQueryTagCanvasScriptFlag) { + // load one script + jQueryTagCanvasScriptFlag = true; + document.write("<scr" + "ipt type=\"text/javascript\" src=\"<?= url::base(false).gallery::find_file("js", "jquery.tagcanvas.mod.min.js", false) ?>\"></scr" + "ipt>"); + }; + setTimeout("initScripts()", 50); + } else { + // libraries loaded - run actual code + function redraw() { + // set g-tag-cloud-html5-embed-canvas size + $("#g-tag-cloud-html5-embed-canvas").attr({ + 'width' : $("#g-tag-cloud-html5-embed").parent().width(), + 'height': $("#g-tag-cloud-html5-embed").parent().height() + }); + // start g-tag-cloud-html5-embed-canvas + if(!$('#g-tag-cloud-html5-embed-canvas').tagcanvas(<?= $options ?>,'g-tag-cloud-html5-embed-tags')) { + // something went wrong, hide the canvas container g-tag-cloud-html5-embed + $('#g-tag-cloud-html5-embed').hide(); + }; + }; + // resize and redraw the canvas + $(document).ready(redraw); + $(window).resize(redraw); + }; + }; + initScripts(); +</script> +<div id="g-tag-cloud-html5-embed"> + <canvas id="g-tag-cloud-html5-embed-canvas"> + <? echo t('Tag cloud loading...'); ?> + </canvas> +</div> +<div id="g-tag-cloud-html5-embed-tags" style="display: none"> + <?= $cloud ?> +</div>
\ No newline at end of file diff --git a/modules/tag_cloud_html5/views/tag_cloud_html5_page.html.php b/modules/tag_cloud_html5/views/tag_cloud_html5_page.html.php new file mode 100644 index 0000000..2b6fa62 --- /dev/null +++ b/modules/tag_cloud_html5/views/tag_cloud_html5_page.html.php @@ -0,0 +1,40 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<!--[if lt IE 9]> +<?= html::script(gallery::find_file("js", "excanvas.compiled.js", false)) ?> +<![endif]--> +<script type="text/javascript"> + function redraw() { + + // set g-tag-cloud-html5-page-canvas size + $("#g-tag-cloud-html5-page-canvas").attr({ + 'width' : Math.floor(Math.min( $(window).height()*<?= $width ?>, $("#g-tag-cloud-html5-page").width() )), + 'height': Math.floor( $(window).height()*<?= $height ?> ) + }); + + // start g-tag-cloud-html5-page-canvas + if(!$('#g-tag-cloud-html5-page-canvas').tagcanvas(<?= $options ?>,'g-tag-cloud-html5-page-tags')) { + // something went wrong, hide the canvas container g-tag-cloud-html5-page + $('#g-tag-cloud-html5-page').hide(); + }; + + }; + + // resize and redraw the canvas + $(window).resize(redraw); + $(document).ready(redraw); +</script> +<div id="g-tag-cloud-html5-page-header"> + <div id="g-tag-cloud-html5-page-buttons"> + <?= $theme->dynamic_top() ?> + </div> + <h1><?= html::clean($title) ?></h1> +</div> +<div id="g-tag-cloud-html5-page"> + <canvas id="g-tag-cloud-html5-page-canvas"> + <? echo t('Tag cloud loading...'); ?> + </canvas> +</div> +<div id="g-tag-cloud-html5-page-tags"> + <?= $cloud ?> +</div> +<?= $theme->dynamic_bottom() ?>
\ No newline at end of file |
