diff --git a/admin/404.php b/admin/404.php new file mode 100644 index 0000000..e69de29 diff --git a/admin/config.php b/admin/config.php new file mode 100644 index 0000000..e900dd9 --- /dev/null +++ b/admin/config.php @@ -0,0 +1,140 @@ + + * @version 2.5.7 + * @package Flogr + * @link http://flogr.googlecode.com + */ + +/** + * Your Flickr user id and/or group id + * + * Note: A flickr id (not name) is needed. You can lookup your id at + * http://idgettr.com/. + * + * User id only - + * Provide only your flickr user id and flogr will use the user photostream + * + * Group id only - + * Provide only your flickr group id and flogr will use the group photostream + * + * User and Group id - + * Provide both your user and group ids and flogr will show only the photos + * you have posted to the given group. + */ +OPTIONAL_SETTING('FLICKR_USER_ID', '95137114@N00'); +OPTIONAL_SETTING('FLICKR_GROUP_ID', ''); +//OPTIONAL_SETTING('FLICKR_USER_ID', ''); +//OPTIONAL_SETTING('FLICKR_GROUP_ID', '82648219@N00'); + +/** + * Site settings + */ +REQUIRED_SETTING('SITE_TITLE', 'Flogr'); +REQUIRED_SETTING('SITE_DESCRIPTION', 'A photoblog application built on Flickr'); +REQUIRED_SETTING('SITE_THEME', 'blackstripe'); +REQUIRED_SETTING('SITE_THEME_PATH', 'themes/' . SITE_THEME . '/'); +$url = "http://". $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI']; +REQUIRED_SETTING('SITE_URL', substr($url, 0, strrpos($url, "/"))); + +/************************************************************************ + * Flogr settings + ************************************************************************/ + +/* EXIF tags to include */ +REQUIRED_SETTING('FLOGR_EXIF', 'Make,Model,Software,Exposure,Aperture,Shutter Speed,ISO Speed,Flash'); + +/* + * Quality (resolution) of the photo on the main photo page. Can me: + * + * Square: Small square 75x75 + * Thumbnail: 100 on longest side + * Small: 240 on longest side + * Medium: 500 on longest side + * Medium640: 640 on longest side + * Large: 1024 on longest side + * Original: Original size + * + * Note: Before May 25th 2010 large photos only exist for very large original + * images. Also I highly recommend against setting this to "Original" since + * it can take considerably longer to download high resolution original photos + * and it is unecessary since the photos will be scaled down to + * FLOGR_MAIN_PHOTO_SIZE anyway. + */ +REQUIRED_SETTING('FLOGR_PHOTO_QUALITY', 'Medium640'); + +/* Desired width of main photo */ +REQUIRED_SETTING('FLOGR_MAIN_PHOTO_SIZE', 750); + +/* + * Quality (resolution) of the photo in the slideshow. Can me: + * + * Square: Small square 75x75 + * Thumbnail: 100 on longest side + * Small: 240 on longest side + * Medium: 500 on longest side + * Medium640: 640 on longest side + * Large: 1024 on longest side + * Original: Original size + * + * Note: Before May 25th 2010 large photos only exist for very large original + * images. Also I highly recommend against setting this to "Original" since + * it can take considerably longer to download high resolution original photos. + */ +REQUIRED_SETTING('FLOGR_SLIDESHOW_PHOTO_QUALITY', 'Medium640'); + +/* Photosets to include - separate multiple sets with commas. */ +OPTIONAL_SETTING('FLOGR_PHOTOSETS_INCLUDE', ''); + +/* Number of tags to include in the tag cloud */ +REQUIRED_SETTING('FLOGR_TAGS_COUNT', 200); + +/* Tags to include - separate multiple tags with commas */ +OPTIONAL_SETTING('FLOGR_TAGS_INCLUDE', ''); + +/* Hide FLOGR_TAGS_INCLUDE from tag cloud */ +OPTIONAL_SETTING('FLOGR_TAGS_INCLUDE_HIDE', ''); + +/* PHP date format - http://us2.php.net/date */ +REQUIRED_SETTING('FLOGR_DATE_FORMAT', 'F j, Y'); + +/* Set to true to use date taken - false to use date uploaded */ +REQUIRED_SETTING('FLOGR_SHOW_DATE_TAKEN', true); + +/* Number of thumbnails to show on the 'recent' page */ +REQUIRED_SETTING('FLOGR_THUMBNAILS_PER_PAGE', 48); + +/** + * The order in which to sort returned photos. Deafults to date-posted-desc + * (unless you are doing a radial geo query, in which case the default sorting + * is by ascending distance from the point specified). The possible values are: + * date-posted-asc, date-posted-desc, date-taken-asc, date-taken-desc, + * interestingness-desc, interestingness-asc, and relevance. + */ +OPTIONAL_SETTING('FLOGR_SORT', ''); + +/* Log level */ + +/** + * Constants + */ +define('FLOGR_LOG_NONE', 0); +define('FLOGR_LOG_ERR', 1); +define('FLOGR_LOG_WARNING', 2); +define('FLOGR_LOG_DEBUG', 3); +REQUIRED_SETTING('FLOGR_LOG_LEVEL', FLOGR_LOG_ERR); + +/** + * Cache Settings + * + * To improve performance flogr can be configured to cache photos to a MySql database + * or to the web server. + */ +OPTIONAL_SETTING('CACHE_SQL_USER', ''); +OPTIONAL_SETTING('CACHE_SQL_PASSWORD', ''); +OPTIONAL_SETTING('CACHE_SQL_SERVER', ''); +OPTIONAL_SETTING('CACHE_SQL_DATABASE', ''); +OPTIONAL_SETTING('CACHE_PATH', ''); +?> diff --git a/admin/flogr.php b/admin/flogr.php new file mode 100644 index 0000000..93b8a84 --- /dev/null +++ b/admin/flogr.php @@ -0,0 +1,159 @@ + + * @version 2.5.7 + * @package Flogr + * @link http://flogr.googlecode.com + */ + +/** + * Defines a required constant - quits if value not set. + * + * @param string name Name of constant + * @param string value Value for constant + */ +function REQUIRED_SETTING($name, $value) { + if (!isset($value) || $value == '') { + die("

You need to set '{$name}' in admin\config.php before you can being using flogr.

"); + } + define($name, $value, true); +} + +/** + * Defines an optional constant - wrapper for define(). + * + * @param string name Name of constant + * @param string value Value for constant + */ +function OPTIONAL_SETTING($name, $value) { + define($name, $value, true); +} + +/** + * Decides which include path delimiter to use. Windows should be using a semi-colon + * and everything else should be using a colon. If this isn't working on your system, + * comment out this if statement and manually set the correct value into $path_delimiter. + */ +if (strpos(__FILE__, ':') !== false) { + $path_delimiter = ';'; +} else { + $path_delimiter = ':'; +} + +ini_set('include_path', + dirname(__FILE__) . '/../include/PEAR' . $path_delimiter . + dirname(__FILE__) . '/../include/phpFlickr-2.1.0' . $path_delimiter . + dirname(__FILE__) . '/../admin' . $path_delimiter . + dirname(__FILE__) . '/../pages' . $path_delimiter . + ini_get('include_path')); + +/** + * Hide PHP warnings...nobody's perfect :) + */ +error_reporting(E_ERROR); + +/** + * Includes + */ +require_once('phpFlickr.php'); +require_once('Log.php'); +require_once('config.php'); + +/** + * Create the flogr instance and let's get going + */ +if (!isset($flogr)) + $flogr = new Flogr(); +$flogr->run(); + +/** + * The Flogr:: class is responsible for mapping requests to the appropriate + * handler page based on the type request parameter (ex 'index.php?type=photo') + * and setting up logging. + * + * @author Mike Carruth + * @package Flogr + */ +class Flogr { + /* + * Maps request type to template page. + * + * @var array + */ + + var $_pageMap = array( + '' => 'photo.php', + 'photo' => 'photo.php', + 'recent' => 'recent.php', + 'sets' => 'sets.php', + 'tags' => 'tags.php', + 'map' => 'map.php', + 'map_data' => 'map_data.php', + 'favorites' => 'favorites.php', + 'about' => 'about.php', + 'rss' => 'rss.php' + ); + + /* + * Maps flogr log levels to PEAR::LOG + */ + var $_logLevels = array( + FLOGR_LOG_NONE => PEAR_LOG_NONE, + FLOGR_LOG_ERR => PEAR_LOG_ERR, + FLOGR_LOG_WARNING => PEAR_LOG_WARNING, + FLOGR_LOG_DEBUG => PEAR_LOG_DEBUG + ); + var $_logMask = PEAR_LOG_ALL; + var $_logger = null; + var $_logHandlers = array( + 'firebug' => null, + ); + + function __construct() { + + $this->_logger = &Log::singleton('composite', '', '', null, $this->_logLevels[FLOGR_LOG_LEVEL]); + $keys = array_keys($this->_logHandlers); + foreach ($keys as $key) { + $handler = &Log::singleton($key, '', '', null, $this->_logLevels[FLOGR_LOG_LEVEL]); + $this->_logHandlers[$key] = $handler; + $this->_logger->addChild($handler); + } + } + + function logInfo($string) { + $this->_logger->info($string); + } + + function logWarning($string) { + $this->_logger->warning($string); + } + + function logErr($string) { + $this->_logger->err($string); + } + + function logDebug($string) { + $this->_logger->debug($string); + } + + function run() { + if (defined('FLICKR_USER_ID') || defined('FLICKR_GROUP_ID')) { + if ($_GET['type'] != 'rss' && $_GET['type'] != 'map_data') { + include('header.php'); + include(SITE_THEME_PATH . 'header.php'); + } + + include($this->_pageMap[$_GET['type']]); + include(SITE_THEME_PATH . $this->_pageMap[$_GET['type']]); + + if ($_GET['type'] != 'rss' && $_GET['type'] != 'map_data') { + include(SITE_THEME_PATH . 'footer.php'); + include('footer.php'); + } + } + } + +} + +?> diff --git a/admin/footer.php b/admin/footer.php new file mode 100644 index 0000000..67f9066 --- /dev/null +++ b/admin/footer.php @@ -0,0 +1,12 @@ + + + + diff --git a/admin/header.php b/admin/header.php new file mode 100644 index 0000000..a6f9768 --- /dev/null +++ b/admin/header.php @@ -0,0 +1,10 @@ + + + + + + <?php echo SITE_TITLE; ?> + + + + \ No newline at end of file diff --git a/admin/profiler.php b/admin/profiler.php new file mode 100644 index 0000000..bcf0b25 --- /dev/null +++ b/admin/profiler.php @@ -0,0 +1,43 @@ +_scopeText = $scopeText; + $this->_startTime = microtime(true); + $this->_warnTime = $warnTime; + $this->_errTime = $errTime; + + if ($this->_scopeText == '') { + $debug_backtrace = debug_backtrace(); + $class_name = $debug_backtrace[1]["class"]; + $function_name = $debug_backtrace[1]["function"]; + $line_number = $debug_backtrace[1]["line"]; + $this->_scopeText = $class_name . "::" . $function_name . "(" . $line_number . "):"; + } + } + + function __destruct() { + global $flogr; + + $endTime = microtime(true); + $elapsedTime = round($endTime - $this->_startTime, 2); + + $message = $this->_scopeText . " completed in {$elapsedTime}s"; + + if ($elapsedTime < $this->_warnTime) { + $flogr->logInfo($message); + } else if ($elapsedTime < $this->_errTime) { + $flogr->logWarning($message); + } else { + $flogr->logErr($message); + } + } +} +?> \ No newline at end of file diff --git a/doc/readme.txt b/doc/readme.txt new file mode 100644 index 0000000..0760e00 --- /dev/null +++ b/doc/readme.txt @@ -0,0 +1,187 @@ +=============================================================================== +flogr - Customizable photo gallery for photos hosted on Flickr + + Author: Mike Carruth (mikecarruth [at] google [dot] com + Homepage: http://code.google.com/p/flogr/ + Newsgroup: http://groups.google.com/group/flogr +=============================================================================== + +--------------------------------------------------------------------- + Introduction +--------------------------------------------------------------------- + + Flogr is a photoblog application powered by PHP, MySQL, and + Flickr. It is an easy to setup and easy to customize alternative + for folks that currently hack their own photoblog using MT or + Wordpress, or those trying to make Gallery or PixelPost work with + Flickr. If you use Flickr and want to have your own photoblog, + but don't want to hack blog software or upload the same pictures + multiple times (to flickr and pixelpost for example) then flogr + may be what you're after. + +--------------------------------------------------------------------- + Features +--------------------------------------------------------------------- + + * Customizable photoblog interface for your flickr photos + * Photo details overlay displays description, EXIF data, tags, geo data and comments + * Thumbnail viewer displays photos by date taken, photoset, and tag + * Slimbox photo slideshow + * Flickr tag cloud page + * RSS 2.0 support + +--------------------------------------------------------------------- + Credits +--------------------------------------------------------------------- + + * phpFlickr + * Slimbox + * JQuery + +--------------------------------------------------------------------- + Requirements +--------------------------------------------------------------------- + + - PHP 4 or > + - Mysql (optional - used to cache images for improved performance) + +--------------------------------------------------------------------- + Setup +--------------------------------------------------------------------- + + 1. Update the required application options inc/config.php + 2. Cconfigure filesystem or database caching (optional) + 3. Customize the indicated section of about.php (optional) + 4. Copy all files from into a folder in your web server. + +--------------------------------------------------------------------- + Download +--------------------------------------------------------------------- + + http://code.google.com/p/flogr/downloads/list + +--------------------------------------------------------------------- + Support +--------------------------------------------------------------------- + + Bugs and Feature Requests + - http://code.google.com/p/flogr/issues/list + + Wiki + - http://code.google.com/p/flogr/w/list + + Forum + - http://groups.google.com/group/flogr + +--------------------------------------------------------------------- + History +--------------------------------------------------------------------- + + Version 2.4 (November 7, 2010) + * Added map to photo info overlay + * Reformatted link in slimbox slideshow + + Version 2.3 (December 10, 2009) + * Issue #70 - Last image not displayed on main page + * Updated to Slimbox2 w/JQuery + * Changed main page navigation system + * Reformatted photo info overlay + * Issue #36 - Added location data w/links to photo info overlay + + Version 2.2 (November 26, 2009) + * Add support for Flickr Groups + * Changed default photo size to 'Medium' + * Changed icon on slideshow caption link + + Version 2.1 (June 6, 2009) + * Fixed RSS issue + + Version 2.0 (May 22, 2009) + * Major redesign to allow easier creation of themes + * Major refactoring to optimize performance + * Integrated Firebug logging + * Fixed several issues + + Version 1.7.2 (December 1, 2007) + * Fixed issue #30 Photo size configuration + * Fixed issue #31 Removed mouseover/mouseout effects + * Fixed issue #32 Forced utf-8 char encoding + * Fixed issue #33 Use photo specific copyright notice + + Version 1.7.1 (November 20, 2007) + * Fixed issue #29 RSS feed timing out and not validating + + Version 1.7 (November 18, 2007) + * Redesigned details overlay div + * Redesigned tag cloud page + * Redesigned about page + * Added support for flickr groups + * Fixed issue #27 - Details overlay div is sometimes not sized correctly + * Fixed issue #24 - Problem with accentuated characters + + Version 1.6 (November 11, 2007) + * Fixed bug with html in photo/set title or description + * Fixed bug where supplied filter tags were ignored for previous photos + * Redesigned photo details fly-in div + * Moved exif labels into config file + * Moved main photo size into config file + * Refactored index.php and recent.php + * Added tag cloud + * Added support for browsing by tag + + Version 1.5 (August 6, 2007) + * Performance improvements + * Fixed several issues around the main page photo + * Fixed a security issue where db settings were visible + * Added favorites page + * Added photo tooltip effect + * Modified photo details popup + * Adjusted layout of recent and sets pages + * Update mootools/slimbox + + Version 1.4 (May 19, 2007) + * Changed set thumbnail to use user selected photo + * Moved photo comments, description and exif data to div overlay + + Version 1.3.1 (May 17, 2007) + * Fixed IE "Invalid Operation" bug on main page + * Hide next/prev buttons when no more pages + * Fixed info page footer wrapping issue in IE + + Version 1.3 (May 16, 2007) + * Changed main photo date to be date posted (instead of date taken) + * Add more exif fields + * Fixed a bug where main photo was not rendered when original upload size was not available + + Version 1.2 (May 13, 2007) + * Added RSS feed + * Fixed EXIF photo bug where ISO speed was not being shown + + Version 1.1 (May 10, 2007) + * Added flogr home page link to info page + + Version 1.0 (May 8, 2007) + * Initial Release + +--------------------------------------------------------------------- + License +--------------------------------------------------------------------- + Copyright (c) 2009 Mike Carruth + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. \ No newline at end of file diff --git a/include/PEAR/DB.php b/include/PEAR/DB.php new file mode 100644 index 0000000..a3eede0 --- /dev/null +++ b/include/PEAR/DB.php @@ -0,0 +1,1388 @@ + + * @author Tomas V.V.Cox + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: DB.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the PEAR class so it can be extended from + */ +require_once 'PEAR.php'; + + +// {{{ constants +// {{{ error codes + +/**#@+ + * One of PEAR DB's portable error codes. + * @see DB_common::errorCode(), DB::errorMessage() + * + * {@internal If you add an error code here, make sure you also add a textual + * version of it in DB::errorMessage().}} + */ + +/** + * The code returned by many methods upon success + */ +define('DB_OK', 1); + +/** + * Unkown error + */ +define('DB_ERROR', -1); + +/** + * Syntax error + */ +define('DB_ERROR_SYNTAX', -2); + +/** + * Tried to insert a duplicate value into a primary or unique index + */ +define('DB_ERROR_CONSTRAINT', -3); + +/** + * An identifier in the query refers to a non-existant object + */ +define('DB_ERROR_NOT_FOUND', -4); + +/** + * Tried to create a duplicate object + */ +define('DB_ERROR_ALREADY_EXISTS', -5); + +/** + * The current driver does not support the action you attempted + */ +define('DB_ERROR_UNSUPPORTED', -6); + +/** + * The number of parameters does not match the number of placeholders + */ +define('DB_ERROR_MISMATCH', -7); + +/** + * A literal submitted did not match the data type expected + */ +define('DB_ERROR_INVALID', -8); + +/** + * The current DBMS does not support the action you attempted + */ +define('DB_ERROR_NOT_CAPABLE', -9); + +/** + * A literal submitted was too long so the end of it was removed + */ +define('DB_ERROR_TRUNCATED', -10); + +/** + * A literal number submitted did not match the data type expected + */ +define('DB_ERROR_INVALID_NUMBER', -11); + +/** + * A literal date submitted did not match the data type expected + */ +define('DB_ERROR_INVALID_DATE', -12); + +/** + * Attempt to divide something by zero + */ +define('DB_ERROR_DIVZERO', -13); + +/** + * A database needs to be selected + */ +define('DB_ERROR_NODBSELECTED', -14); + +/** + * Could not create the object requested + */ +define('DB_ERROR_CANNOT_CREATE', -15); + +/** + * Could not drop the database requested because it does not exist + */ +define('DB_ERROR_CANNOT_DROP', -17); + +/** + * An identifier in the query refers to a non-existant table + */ +define('DB_ERROR_NOSUCHTABLE', -18); + +/** + * An identifier in the query refers to a non-existant column + */ +define('DB_ERROR_NOSUCHFIELD', -19); + +/** + * The data submitted to the method was inappropriate + */ +define('DB_ERROR_NEED_MORE_DATA', -20); + +/** + * The attempt to lock the table failed + */ +define('DB_ERROR_NOT_LOCKED', -21); + +/** + * The number of columns doesn't match the number of values + */ +define('DB_ERROR_VALUE_COUNT_ON_ROW', -22); + +/** + * The DSN submitted has problems + */ +define('DB_ERROR_INVALID_DSN', -23); + +/** + * Could not connect to the database + */ +define('DB_ERROR_CONNECT_FAILED', -24); + +/** + * The PHP extension needed for this DBMS could not be found + */ +define('DB_ERROR_EXTENSION_NOT_FOUND',-25); + +/** + * The present user has inadequate permissions to perform the task requestd + */ +define('DB_ERROR_ACCESS_VIOLATION', -26); + +/** + * The database requested does not exist + */ +define('DB_ERROR_NOSUCHDB', -27); + +/** + * Tried to insert a null value into a column that doesn't allow nulls + */ +define('DB_ERROR_CONSTRAINT_NOT_NULL',-29); +/**#@-*/ + + +// }}} +// {{{ prepared statement-related + + +/**#@+ + * Identifiers for the placeholders used in prepared statements. + * @see DB_common::prepare() + */ + +/** + * Indicates a scalar (?) placeholder was used + * + * Quote and escape the value as necessary. + */ +define('DB_PARAM_SCALAR', 1); + +/** + * Indicates an opaque (&) placeholder was used + * + * The value presented is a file name. Extract the contents of that file + * and place them in this column. + */ +define('DB_PARAM_OPAQUE', 2); + +/** + * Indicates a misc (!) placeholder was used + * + * The value should not be quoted or escaped. + */ +define('DB_PARAM_MISC', 3); +/**#@-*/ + + +// }}} +// {{{ binary data-related + + +/**#@+ + * The different ways of returning binary data from queries. + */ + +/** + * Sends the fetched data straight through to output + */ +define('DB_BINMODE_PASSTHRU', 1); + +/** + * Lets you return data as usual + */ +define('DB_BINMODE_RETURN', 2); + +/** + * Converts the data to hex format before returning it + * + * For example the string "123" would become "313233". + */ +define('DB_BINMODE_CONVERT', 3); +/**#@-*/ + + +// }}} +// {{{ fetch modes + + +/**#@+ + * Fetch Modes. + * @see DB_common::setFetchMode() + */ + +/** + * Indicates the current default fetch mode should be used + * @see DB_common::$fetchmode + */ +define('DB_FETCHMODE_DEFAULT', 0); + +/** + * Column data indexed by numbers, ordered from 0 and up + */ +define('DB_FETCHMODE_ORDERED', 1); + +/** + * Column data indexed by column names + */ +define('DB_FETCHMODE_ASSOC', 2); + +/** + * Column data as object properties + */ +define('DB_FETCHMODE_OBJECT', 3); + +/** + * For multi-dimensional results, make the column name the first level + * of the array and put the row number in the second level of the array + * + * This is flipped from the normal behavior, which puts the row numbers + * in the first level of the array and the column names in the second level. + */ +define('DB_FETCHMODE_FLIPPED', 4); +/**#@-*/ + +/**#@+ + * Old fetch modes. Left here for compatibility. + */ +define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED); +define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC); +define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED); +/**#@-*/ + + +// }}} +// {{{ tableInfo() && autoPrepare()-related + + +/**#@+ + * The type of information to return from the tableInfo() method. + * + * Bitwised constants, so they can be combined using | + * and removed using ^. + * + * @see DB_common::tableInfo() + * + * {@internal Since the TABLEINFO constants are bitwised, if more of them are + * added in the future, make sure to adjust DB_TABLEINFO_FULL accordingly.}} + */ +define('DB_TABLEINFO_ORDER', 1); +define('DB_TABLEINFO_ORDERTABLE', 2); +define('DB_TABLEINFO_FULL', 3); +/**#@-*/ + + +/**#@+ + * The type of query to create with the automatic query building methods. + * @see DB_common::autoPrepare(), DB_common::autoExecute() + */ +define('DB_AUTOQUERY_INSERT', 1); +define('DB_AUTOQUERY_UPDATE', 2); +/**#@-*/ + + +// }}} +// {{{ portability modes + + +/**#@+ + * Portability Modes. + * + * Bitwised constants, so they can be combined using | + * and removed using ^. + * + * @see DB_common::setOption() + * + * {@internal Since the PORTABILITY constants are bitwised, if more of them are + * added in the future, make sure to adjust DB_PORTABILITY_ALL accordingly.}} + */ + +/** + * Turn off all portability features + */ +define('DB_PORTABILITY_NONE', 0); + +/** + * Convert names of tables and fields to lower case + * when using the get*(), fetch*() and tableInfo() methods + */ +define('DB_PORTABILITY_LOWERCASE', 1); + +/** + * Right trim the data output by get*() and fetch*() + */ +define('DB_PORTABILITY_RTRIM', 2); + +/** + * Force reporting the number of rows deleted + */ +define('DB_PORTABILITY_DELETE_COUNT', 4); + +/** + * Enable hack that makes numRows() work in Oracle + */ +define('DB_PORTABILITY_NUMROWS', 8); + +/** + * Makes certain error messages in certain drivers compatible + * with those from other DBMS's + * + * + mysql, mysqli: change unique/primary key constraints + * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT + * + * + odbc(access): MS's ODBC driver reports 'no such field' as code + * 07001, which means 'too few parameters.' When this option is on + * that code gets mapped to DB_ERROR_NOSUCHFIELD. + */ +define('DB_PORTABILITY_ERRORS', 16); + +/** + * Convert null values to empty strings in data output by + * get*() and fetch*() + */ +define('DB_PORTABILITY_NULL_TO_EMPTY', 32); + +/** + * Turn on all portability features + */ +define('DB_PORTABILITY_ALL', 63); +/**#@-*/ + +// }}} + + +// }}} +// {{{ class DB + +/** + * Database independent query interface + * + * The main "DB" class is simply a container class with some static + * methods for creating DB objects as well as some utility functions + * common to all parts of DB. + * + * The object model of DB is as follows (indentation means inheritance): + *
+ * DB           The main DB class.  This is simply a utility class
+ *              with some "static" methods for creating DB objects as
+ *              well as common utility functions for other DB classes.
+ *
+ * DB_common    The base for each DB implementation.  Provides default
+ * |            implementations (in OO lingo virtual methods) for
+ * |            the actual DB implementations as well as a bunch of
+ * |            query utility functions.
+ * |
+ * +-DB_mysql   The DB implementation for MySQL.  Inherits DB_common.
+ *              When calling DB::factory or DB::connect for MySQL
+ *              connections, the object returned is an instance of this
+ *              class.
+ * 
+ * + * @category Database + * @package DB + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB +{ + // {{{ &factory() + + /** + * Create a new DB object for the specified database type but don't + * connect to the database + * + * @param string $type the database type (eg "mysql") + * @param array $options an associative array of option names and values + * + * @return object a new DB object. A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function &factory($type, $options = false) + { + if (!is_array($options)) { + $options = array('persistent' => $options); + } + + if (isset($options['debug']) && $options['debug'] >= 2) { + // expose php errors with sufficient debug level + include_once "DB/{$type}.php"; + } else { + @include_once "DB/{$type}.php"; + } + + $classname = "DB_${type}"; + + if (!class_exists($classname)) { + $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, + "Unable to include the DB/{$type}.php" + . " file for '$dsn'", + 'DB_Error', true); + return $tmp; + } + + @$obj =& new $classname; + + foreach ($options as $option => $value) { + $test = $obj->setOption($option, $value); + if (DB::isError($test)) { + return $test; + } + } + + return $obj; + } + + // }}} + // {{{ &connect() + + /** + * Create a new DB object including a connection to the specified database + * + * Example 1. + * + * require_once 'DB.php'; + * + * $dsn = 'pgsql://user:password@host/database'; + * $options = array( + * 'debug' => 2, + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db =& DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * + * + * @param mixed $dsn the string "data source name" or array in the + * format returned by DB::parseDSN() + * @param array $options an associative array of option names and values + * + * @return object a new DB object. A DB_Error object on failure. + * + * @uses DB_dbase::connect(), DB_fbsql::connect(), DB_ibase::connect(), + * DB_ifx::connect(), DB_msql::connect(), DB_mssql::connect(), + * DB_mysql::connect(), DB_mysqli::connect(), DB_oci8::connect(), + * DB_odbc::connect(), DB_pgsql::connect(), DB_sqlite::connect(), + * DB_sybase::connect() + * + * @uses DB::parseDSN(), DB_common::setOption(), PEAR::isError() + */ + function &connect($dsn, $options = array()) + { + $dsninfo = DB::parseDSN($dsn); + $type = $dsninfo['phptype']; + + if (!is_array($options)) { + /* + * For backwards compatibility. $options used to be boolean, + * indicating whether the connection should be persistent. + */ + $options = array('persistent' => $options); + } + + if (isset($options['debug']) && $options['debug'] >= 2) { + // expose php errors with sufficient debug level + include_once "DB/${type}.php"; + } else { + @include_once "DB/${type}.php"; + } + + $classname = "DB_${type}"; + if (!class_exists($classname)) { + $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, + "Unable to include the DB/{$type}.php" + . " file for '$dsn'", + 'DB_Error', true); + return $tmp; + } + + @$obj =& new $classname; + + foreach ($options as $option => $value) { + $test = $obj->setOption($option, $value); + if (DB::isError($test)) { + return $test; + } + } + + $err = $obj->connect($dsninfo, $obj->getOption('persistent')); + if (DB::isError($err)) { + $err->addUserInfo($dsn); + return $err; + } + + return $obj; + } + + // }}} + // {{{ apiVersion() + + /** + * Return the DB API version + * + * @return string the DB API version number + */ + function apiVersion() + { + return '@package_version@'; + } + + // }}} + // {{{ isError() + + /** + * Determines if a variable is a DB_Error object + * + * @param mixed $value the variable to check + * + * @return bool whether $value is DB_Error object + */ + function isError($value) + { + return is_a($value, 'DB_Error'); + } + + // }}} + // {{{ isConnection() + + /** + * Determines if a value is a DB_ object + * + * @param mixed $value the value to test + * + * @return bool whether $value is a DB_ object + */ + function isConnection($value) + { + return (is_object($value) && + is_subclass_of($value, 'db_common') && + method_exists($value, 'simpleQuery')); + } + + // }}} + // {{{ isManip() + + /** + * Tell whether a query is a data manipulation or data definition query + * + * Examples of data manipulation queries are INSERT, UPDATE and DELETE. + * Examples of data definition queries are CREATE, DROP, ALTER, GRANT, + * REVOKE. + * + * @param string $query the query + * + * @return boolean whether $query is a data manipulation query + */ + function isManip($query) + { + $manips = 'INSERT|UPDATE|DELETE|REPLACE|' + . 'CREATE|DROP|' + . 'LOAD DATA|SELECT .* INTO|COPY|' + . 'ALTER|GRANT|REVOKE|' + . 'LOCK|UNLOCK'; + if (preg_match('/^\s*"?(' . $manips . ')\s+/i', $query)) { + return true; + } + return false; + } + + // }}} + // {{{ errorMessage() + + /** + * Return a textual error message for a DB error code + * + * @param integer $value the DB error code + * + * @return string the error message or false if the error code was + * not recognized + */ + function errorMessage($value) + { + static $errorMessages; + if (!isset($errorMessages)) { + $errorMessages = array( + DB_ERROR => 'unknown error', + DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions', + DB_ERROR_ALREADY_EXISTS => 'already exists', + DB_ERROR_CANNOT_CREATE => 'can not create', + DB_ERROR_CANNOT_DROP => 'can not drop', + DB_ERROR_CONNECT_FAILED => 'connect failed', + DB_ERROR_CONSTRAINT => 'constraint violation', + DB_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', + DB_ERROR_DIVZERO => 'division by zero', + DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', + DB_ERROR_INVALID => 'invalid', + DB_ERROR_INVALID_DATE => 'invalid date or time', + DB_ERROR_INVALID_DSN => 'invalid DSN', + DB_ERROR_INVALID_NUMBER => 'invalid number', + DB_ERROR_MISMATCH => 'mismatch', + DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied', + DB_ERROR_NODBSELECTED => 'no database selected', + DB_ERROR_NOSUCHDB => 'no such database', + DB_ERROR_NOSUCHFIELD => 'no such field', + DB_ERROR_NOSUCHTABLE => 'no such table', + DB_ERROR_NOT_CAPABLE => 'DB backend not capable', + DB_ERROR_NOT_FOUND => 'not found', + DB_ERROR_NOT_LOCKED => 'not locked', + DB_ERROR_SYNTAX => 'syntax error', + DB_ERROR_UNSUPPORTED => 'not supported', + DB_ERROR_TRUNCATED => 'truncated', + DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', + DB_OK => 'no error', + ); + } + + if (DB::isError($value)) { + $value = $value->getCode(); + } + + return isset($errorMessages[$value]) ? $errorMessages[$value] + : $errorMessages[DB_ERROR]; + } + + // }}} + // {{{ parseDSN() + + /** + * Parse a data source name + * + * Additional keys can be added by appending a URI query string to the + * end of the DSN. + * + * The format of the supplied DSN is in its fullest form: + * + * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true + * + * + * Most variations are allowed: + * + * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 + * phptype://username:password@hostspec/database_name + * phptype://username:password@hostspec + * phptype://username@hostspec + * phptype://hostspec/database + * phptype://hostspec + * phptype(dbsyntax) + * phptype + * + * + * @param string $dsn Data Source Name to be parsed + * + * @return array an associative array with the following keys: + * + phptype: Database backend used in PHP (mysql, odbc etc.) + * + dbsyntax: Database used with regards to SQL syntax etc. + * + protocol: Communication protocol to use (tcp, unix etc.) + * + hostspec: Host specification (hostname[:port]) + * + database: Database to use on the DBMS server + * + username: User name for login + * + password: Password for login + */ + function parseDSN($dsn) + { + $parsed = array( + 'phptype' => false, + 'dbsyntax' => false, + 'username' => false, + 'password' => false, + 'protocol' => false, + 'hostspec' => false, + 'port' => false, + 'socket' => false, + 'database' => false, + ); + + if (is_array($dsn)) { + $dsn = array_merge($parsed, $dsn); + if (!$dsn['dbsyntax']) { + $dsn['dbsyntax'] = $dsn['phptype']; + } + return $dsn; + } + + // Find phptype and dbsyntax + if (($pos = strpos($dsn, '://')) !== false) { + $str = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 3); + } else { + $str = $dsn; + $dsn = null; + } + + // Get phptype and dbsyntax + // $str => phptype(dbsyntax) + if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { + $parsed['phptype'] = $arr[1]; + $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2]; + } else { + $parsed['phptype'] = $str; + $parsed['dbsyntax'] = $str; + } + + if (!count($dsn)) { + return $parsed; + } + + // Get (if found): username and password + // $dsn => username:password@protocol+hostspec/database + if (($at = strrpos($dsn,'@')) !== false) { + $str = substr($dsn, 0, $at); + $dsn = substr($dsn, $at + 1); + if (($pos = strpos($str, ':')) !== false) { + $parsed['username'] = rawurldecode(substr($str, 0, $pos)); + $parsed['password'] = rawurldecode(substr($str, $pos + 1)); + } else { + $parsed['username'] = rawurldecode($str); + } + } + + // Find protocol and hostspec + + if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { + // $dsn => proto(proto_opts)/database + $proto = $match[1]; + $proto_opts = $match[2] ? $match[2] : false; + $dsn = $match[3]; + + } else { + // $dsn => protocol+hostspec/database (old format) + if (strpos($dsn, '+') !== false) { + list($proto, $dsn) = explode('+', $dsn, 2); + } + if (strpos($dsn, '/') !== false) { + list($proto_opts, $dsn) = explode('/', $dsn, 2); + } else { + $proto_opts = $dsn; + $dsn = null; + } + } + + // process the different protocol options + $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp'; + $proto_opts = rawurldecode($proto_opts); + if ($parsed['protocol'] == 'tcp') { + if (strpos($proto_opts, ':') !== false) { + list($parsed['hostspec'], + $parsed['port']) = explode(':', $proto_opts); + } else { + $parsed['hostspec'] = $proto_opts; + } + } elseif ($parsed['protocol'] == 'unix') { + $parsed['socket'] = $proto_opts; + } + + // Get dabase if any + // $dsn => database + if ($dsn) { + if (($pos = strpos($dsn, '?')) === false) { + // /database + $parsed['database'] = rawurldecode($dsn); + } else { + // /database?param1=value1¶m2=value2 + $parsed['database'] = rawurldecode(substr($dsn, 0, $pos)); + $dsn = substr($dsn, $pos + 1); + if (strpos($dsn, '&') !== false) { + $opts = explode('&', $dsn); + } else { // database?param1=value1 + $opts = array($dsn); + } + foreach ($opts as $opt) { + list($key, $value) = explode('=', $opt); + if (!isset($parsed[$key])) { + // don't allow params overwrite + $parsed[$key] = rawurldecode($value); + } + } + } + } + + return $parsed; + } + + // }}} +} + +// }}} +// {{{ class DB_Error + +/** + * DB_Error implements a class for reporting portable database error + * messages + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_Error extends PEAR_Error +{ + // {{{ constructor + + /** + * DB_Error constructor + * + * @param mixed $code DB error code, or string with error message + * @param int $mode what "error mode" to operate in + * @param int $level what error level to use for $mode & + * PEAR_ERROR_TRIGGER + * @param mixed $debuginfo additional debug info, such as the last query + * + * @see PEAR_Error + */ + function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null) + { + if (is_int($code)) { + $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code, + $mode, $level, $debuginfo); + } else { + $this->PEAR_Error("DB Error: $code", DB_ERROR, + $mode, $level, $debuginfo); + } + } + + // }}} +} + +// }}} +// {{{ class DB_result + +/** + * This class implements a wrapper for a DB result set + * + * A new instance of this class will be returned by the DB implementation + * after processing a query that returns data. + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_result +{ + // {{{ properties + + /** + * Should results be freed automatically when there are no more rows? + * @var boolean + * @see DB_common::$options + */ + var $autofree; + + /** + * A reference to the DB_ object + * @var object + */ + var $dbh; + + /** + * The current default fetch mode + * @var integer + * @see DB_common::$fetchmode + */ + var $fetchmode; + + /** + * The name of the class into which results should be fetched when + * DB_FETCHMODE_OBJECT is in effect + * + * @var string + * @see DB_common::$fetchmode_object_class + */ + var $fetchmode_object_class; + + /** + * The number of rows to fetch from a limit query + * @var integer + */ + var $limit_count = null; + + /** + * The row to start fetching from in limit queries + * @var integer + */ + var $limit_from = null; + + /** + * The execute parameters that created this result + * @var array + * @since Property available since Release 1.7.0 + */ + var $parameters; + + /** + * The query string that created this result + * + * Copied here incase it changes in $dbh, which is referenced + * + * @var string + * @since Property available since Release 1.7.0 + */ + var $query; + + /** + * The query result resource id created by PHP + * @var resource + */ + var $result; + + /** + * The present row being dealt with + * @var integer + */ + var $row_counter = null; + + /** + * The prepared statement resource id created by PHP in $dbh + * + * This resource is only available when the result set was created using + * a driver's native execute() method, not PEAR DB's emulated one. + * + * Copied here incase it changes in $dbh, which is referenced + * + * {@internal Mainly here because the InterBase/Firebird API is only + * able to retrieve data from result sets if the statemnt handle is + * still in scope.}} + * + * @var resource + * @since Property available since Release 1.7.0 + */ + var $statement; + + + // }}} + // {{{ constructor + + /** + * This constructor sets the object's properties + * + * @param object &$dbh the DB object reference + * @param resource $result the result resource id + * @param array $options an associative array with result options + * + * @return void + */ + function DB_result(&$dbh, $result, $options = array()) + { + $this->autofree = $dbh->options['autofree']; + $this->dbh = &$dbh; + $this->fetchmode = $dbh->fetchmode; + $this->fetchmode_object_class = $dbh->fetchmode_object_class; + $this->parameters = $dbh->last_parameters; + $this->query = $dbh->last_query; + $this->result = $result; + $this->statement = empty($dbh->last_stmt) ? null : $dbh->last_stmt; + foreach ($options as $key => $value) { + $this->setOption($key, $value); + } + } + + /** + * Set options for the DB_result object + * + * @param string $key the option to set + * @param mixed $value the value to set the option to + * + * @return void + */ + function setOption($key, $value = null) + { + switch ($key) { + case 'limit_from': + $this->limit_from = $value; + break; + case 'limit_count': + $this->limit_count = $value; + } + } + + // }}} + // {{{ fetchRow() + + /** + * Fetch a row of data and return it by reference into an array + * + * The type of array returned can be controlled either by setting this + * method's $fetchmode parameter or by changing the default + * fetch mode setFetchMode() before calling this method. + * + * There are two options for standardizing the information returned + * from databases, ensuring their values are consistent when changing + * DBMS's. These portability options can be turned on when creating a + * new DB object or by using setOption(). + * + * + DB_PORTABILITY_LOWERCASE + * convert names of fields to lower case + * + * + DB_PORTABILITY_RTRIM + * right trim the data + * + * @param int $fetchmode the constant indicating how to format the data + * @param int $rownum the row number to fetch (index starts at 0) + * + * @return mixed an array or object containing the row's data, + * NULL when the end of the result set is reached + * or a DB_Error object on failure. + * + * @see DB_common::setOption(), DB_common::setFetchMode() + */ + function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null) + { + if ($fetchmode === DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + if ($fetchmode === DB_FETCHMODE_OBJECT) { + $fetchmode = DB_FETCHMODE_ASSOC; + $object_class = $this->fetchmode_object_class; + } + if ($this->limit_from !== null) { + if ($this->row_counter === null) { + $this->row_counter = $this->limit_from; + // Skip rows + if ($this->dbh->features['limit'] === false) { + $i = 0; + while ($i++ < $this->limit_from) { + $this->dbh->fetchInto($this->result, $arr, $fetchmode); + } + } + } + if ($this->row_counter >= ($this->limit_from + $this->limit_count)) + { + if ($this->autofree) { + $this->free(); + } + $tmp = null; + return $tmp; + } + if ($this->dbh->features['limit'] === 'emulate') { + $rownum = $this->row_counter; + } + $this->row_counter++; + } + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); + if ($res === DB_OK) { + if (isset($object_class)) { + // The default mode is specified in the + // DB_common::fetchmode_object_class property + if ($object_class == 'stdClass') { + $arr = (object) $arr; + } else { + $arr = &new $object_class($arr); + } + } + return $arr; + } + if ($res == null && $this->autofree) { + $this->free(); + } + return $res; + } + + // }}} + // {{{ fetchInto() + + /** + * Fetch a row of data into an array which is passed by reference + * + * The type of array returned can be controlled either by setting this + * method's $fetchmode parameter or by changing the default + * fetch mode setFetchMode() before calling this method. + * + * There are two options for standardizing the information returned + * from databases, ensuring their values are consistent when changing + * DBMS's. These portability options can be turned on when creating a + * new DB object or by using setOption(). + * + * + DB_PORTABILITY_LOWERCASE + * convert names of fields to lower case + * + * + DB_PORTABILITY_RTRIM + * right trim the data + * + * @param array &$arr the variable where the data should be placed + * @param int $fetchmode the constant indicating how to format the data + * @param int $rownum the row number to fetch (index starts at 0) + * + * @return mixed DB_OK if a row is processed, NULL when the end of the + * result set is reached or a DB_Error object on failure + * + * @see DB_common::setOption(), DB_common::setFetchMode() + */ + function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null) + { + if ($fetchmode === DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + if ($fetchmode === DB_FETCHMODE_OBJECT) { + $fetchmode = DB_FETCHMODE_ASSOC; + $object_class = $this->fetchmode_object_class; + } + if ($this->limit_from !== null) { + if ($this->row_counter === null) { + $this->row_counter = $this->limit_from; + // Skip rows + if ($this->dbh->features['limit'] === false) { + $i = 0; + while ($i++ < $this->limit_from) { + $this->dbh->fetchInto($this->result, $arr, $fetchmode); + } + } + } + if ($this->row_counter >= ( + $this->limit_from + $this->limit_count)) + { + if ($this->autofree) { + $this->free(); + } + return null; + } + if ($this->dbh->features['limit'] === 'emulate') { + $rownum = $this->row_counter; + } + + $this->row_counter++; + } + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); + if ($res === DB_OK) { + if (isset($object_class)) { + // default mode specified in the + // DB_common::fetchmode_object_class property + if ($object_class == 'stdClass') { + $arr = (object) $arr; + } else { + $arr = new $object_class($arr); + } + } + return DB_OK; + } + if ($res == null && $this->autofree) { + $this->free(); + } + return $res; + } + + // }}} + // {{{ numCols() + + /** + * Get the the number of columns in a result set + * + * @return int the number of columns. A DB_Error object on failure. + */ + function numCols() + { + return $this->dbh->numCols($this->result); + } + + // }}} + // {{{ numRows() + + /** + * Get the number of rows in a result set + * + * @return int the number of rows. A DB_Error object on failure. + */ + function numRows() + { + if ($this->dbh->features['numrows'] === 'emulate' + && $this->dbh->options['portability'] & DB_PORTABILITY_NUMROWS) + { + if ($this->dbh->features['prepare']) { + $res = $this->dbh->query($this->query, $this->parameters); + } else { + $res = $this->dbh->query($this->query); + } + if (DB::isError($res)) { + return $res; + } + $i = 0; + while ($res->fetchInto($tmp, DB_FETCHMODE_ORDERED)) { + $i++; + } + return $i; + } else { + return $this->dbh->numRows($this->result); + } + } + + // }}} + // {{{ nextResult() + + /** + * Get the next result if a batch of queries was executed + * + * @return bool true if a new result is available or false if not + */ + function nextResult() + { + return $this->dbh->nextResult($this->result); + } + + // }}} + // {{{ free() + + /** + * Frees the resources allocated for this result set + * + * @return bool true on success. A DB_Error object on failure. + */ + function free() + { + $err = $this->dbh->freeResult($this->result); + if (DB::isError($err)) { + return $err; + } + $this->result = false; + $this->statement = false; + return true; + } + + // }}} + // {{{ tableInfo() + + /** + * @see DB_common::tableInfo() + * @deprecated Method deprecated some time before Release 1.2 + */ + function tableInfo($mode = null) + { + if (is_string($mode)) { + return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA); + } + return $this->dbh->tableInfo($this, $mode); + } + + // }}} + // {{{ getQuery() + + /** + * Determine the query string that created this result + * + * @return string the query string + * + * @since Method available since Release 1.7.0 + */ + function getQuery() + { + return $this->query; + } + + // }}} + // {{{ getRowCounter() + + /** + * Tells which row number is currently being processed + * + * @return integer the current row being looked at. Starts at 1. + */ + function getRowCounter() + { + return $this->row_counter; + } + + // }}} +} + +// }}} +// {{{ class DB_row + +/** + * PEAR DB Row Object + * + * The object contains a row of data from a result set. Each column's data + * is placed in a property named for the column. + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + * @see DB_common::setFetchMode() + */ +class DB_row +{ + // {{{ constructor + + /** + * The constructor places a row's data into properties of this object + * + * @param array the array containing the row's data + * + * @return void + */ + function DB_row(&$arr) + { + foreach ($arr as $key => $value) { + $this->$key = &$arr[$key]; + } + } + + // }}} +} + +// }}} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/include/PEAR/DB/common.php b/include/PEAR/DB/common.php new file mode 100644 index 0000000..04e71ff --- /dev/null +++ b/include/PEAR/DB/common.php @@ -0,0 +1,2157 @@ + + * @author Tomas V.V. Cox + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: common.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the PEAR class so it can be extended from + */ +require_once 'PEAR.php'; + +/** + * DB_common is the base class from which each database driver class extends + * + * All common methods are declared here. If a given DBMS driver contains + * a particular method, that method will overload the one here. + * + * @category Database + * @package DB + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_common extends PEAR +{ + // {{{ properties + + /** + * The current default fetch mode + * @var integer + */ + var $fetchmode = DB_FETCHMODE_ORDERED; + + /** + * The name of the class into which results should be fetched when + * DB_FETCHMODE_OBJECT is in effect + * + * @var string + */ + var $fetchmode_object_class = 'stdClass'; + + /** + * Was a connection present when the object was serialized()? + * @var bool + * @see DB_common::__sleep(), DB_common::__wake() + */ + var $was_connected = null; + + /** + * The most recently executed query + * @var string + */ + var $last_query = ''; + + /** + * Run-time configuration options + * + * The 'optimize' option has been deprecated. Use the 'portability' + * option instead. + * + * @var array + * @see DB_common::setOption() + */ + var $options = array( + 'result_buffering' => 500, + 'persistent' => false, + 'ssl' => false, + 'debug' => 0, + 'seqname_format' => '%s_seq', + 'autofree' => false, + 'portability' => DB_PORTABILITY_NONE, + 'optimize' => 'performance', // Deprecated. Use 'portability'. + ); + + /** + * The parameters from the most recently executed query + * @var array + * @since Property available since Release 1.7.0 + */ + var $last_parameters = array(); + + /** + * The elements from each prepared statement + * @var array + */ + var $prepare_tokens = array(); + + /** + * The data types of the various elements in each prepared statement + * @var array + */ + var $prepare_types = array(); + + /** + * The prepared queries + * @var array + */ + var $prepared_queries = array(); + + + // }}} + // {{{ DB_common + + /** + * This constructor calls $this->PEAR('DB_Error') + * + * @return void + */ + function DB_common() + { + $this->PEAR('DB_Error'); + } + + // }}} + // {{{ __sleep() + + /** + * Automatically indicates which properties should be saved + * when PHP's serialize() function is called + * + * @return array the array of properties names that should be saved + */ + function __sleep() + { + if ($this->connection) { + // Don't disconnect(), people use serialize() for many reasons + $this->was_connected = true; + } else { + $this->was_connected = false; + } + if (isset($this->autocommit)) { + return array('autocommit', + 'dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); + } else { + return array('dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); + } + } + + // }}} + // {{{ __wakeup() + + /** + * Automatically reconnects to the database when PHP's unserialize() + * function is called + * + * The reconnection attempt is only performed if the object was connected + * at the time PHP's serialize() function was run. + * + * @return void + */ + function __wakeup() + { + if ($this->was_connected) { + $this->connect($this->dsn, $this->options); + } + } + + // }}} + // {{{ __toString() + + /** + * Automatic string conversion for PHP 5 + * + * @return string a string describing the current PEAR DB object + * + * @since Method available since Release 1.7.0 + */ + function __toString() + { + $info = strtolower(get_class($this)); + $info .= ': (phptype=' . $this->phptype . + ', dbsyntax=' . $this->dbsyntax . + ')'; + if ($this->connection) { + $info .= ' [connected]'; + } + return $info; + } + + // }}} + // {{{ toString() + + /** + * DEPRECATED: String conversion method + * + * @return string a string describing the current PEAR DB object + * + * @deprecated Method deprecated in Release 1.7.0 + */ + function toString() + { + return $this->__toString(); + } + + // }}} + // {{{ quoteString() + + /** + * DEPRECATED: Quotes a string so it can be safely used within string + * delimiters in a query + * + * @param string $string the string to be quoted + * + * @return string the quoted string + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Method deprecated some time before Release 1.2 + */ + function quoteString($string) + { + $string = $this->quote($string); + if ($string{0} == "'") { + return substr($string, 1, -1); + } + return $string; + } + + // }}} + // {{{ quote() + + /** + * DEPRECATED: Quotes a string so it can be safely used in a query + * + * @param string $string the string to quote + * + * @return string the quoted string or the string NULL + * if the value submitted is null. + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Deprecated in release 1.6.0 + */ + function quote($string = null) + { + return ($string === null) ? 'NULL' + : "'" . str_replace("'", "''", $string) . "'"; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * Delimiting style depends on which database driver is being used. + * + * NOTE: just because you CAN use delimited identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (`) -- due to MySQL + * + double quote (") -- due to Oracle + * + brackets ([ or ]) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + odbc(access) + * + odbc(db2) + * + pgsql + * + sqlite + * + sybase (must execute set quoted_identifier on sometime + * prior to use) + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string $str the identifier name to be quoted + * + * @return string the quoted identifier + * + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '"' . str_replace('"', '""', $str) . '"'; + } + + // }}} + // {{{ quoteSmart() + + /** + * Formats input so it can be safely used in a query + * + * The output depends on the PHP data type of input and the database + * type being used. + * + * @param mixed $in the data to be formatted + * + * @return mixed the formatted data. The format depends on the input's + * PHP type: + *
    + *
  • + * input -> returns + *
  • + *
  • + * null -> the string NULL + *
  • + *
  • + * integer or double -> the unquoted number + *
  • + *
  • + * bool -> output depends on the driver in use + * Most drivers return integers: 1 if + * true or 0 if + * false. + * Some return strings: TRUE if + * true or FALSE if + * false. + * Finally one returns strings: T if + * true or F if + * false. Here is a list of each DBMS, + * the values returned and the suggested column type: + *
      + *
    • + * dbase -> T/F + * (Logical) + *
    • + *
    • + * fbase -> TRUE/FALSE + * (BOOLEAN) + *
    • + *
    • + * ibase -> 1/0 + * (SMALLINT) [1] + *
    • + *
    • + * ifx -> 1/0 + * (SMALLINT) [1] + *
    • + *
    • + * msql -> 1/0 + * (INTEGER) + *
    • + *
    • + * mssql -> 1/0 + * (BIT) + *
    • + *
    • + * mysql -> 1/0 + * (TINYINT(1)) + *
    • + *
    • + * mysqli -> 1/0 + * (TINYINT(1)) + *
    • + *
    • + * oci8 -> 1/0 + * (NUMBER(1)) + *
    • + *
    • + * odbc -> 1/0 + * (SMALLINT) [1] + *
    • + *
    • + * pgsql -> TRUE/FALSE + * (BOOLEAN) + *
    • + *
    • + * sqlite -> 1/0 + * (INTEGER) + *
    • + *
    • + * sybase -> 1/0 + * (TINYINT(1)) + *
    • + *
    + * [1] Accommodate the lowest common denominator because not all + * versions of have BOOLEAN. + *
  • + *
  • + * other (including strings and numeric strings) -> + * the data with single quotes escaped by preceeding + * single quotes, backslashes are escaped by preceeding + * backslashes, then the whole string is encapsulated + * between single quotes + *
  • + *
+ * + * @see DB_common::escapeSimple() + * @since Method available since Release 1.6.0 + */ + function quoteSmart($in) + { + if (is_int($in) || is_double($in)) { + return $in; + } elseif (is_bool($in)) { + return $in ? 1 : 0; + } elseif (is_null($in)) { + return 'NULL'; + } else { + return "'" . $this->escapeSimple($in) . "'"; + } + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * In SQLite, this makes things safe for inserts/updates, but may + * cause problems when performing text comparisons against columns + * containing binary data. See the + * {@link http://php.net/sqlite_escape_string PHP manual} for more info. + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + return str_replace("'", "''", $str); + } + + // }}} + // {{{ provides() + + /** + * Tells whether the present driver supports a given feature + * + * @param string $feature the feature you're curious about + * + * @return bool whether this driver supports $feature + */ + function provides($feature) + { + return $this->features[$feature]; + } + + // }}} + // {{{ setFetchMode() + + /** + * Sets the fetch mode that should be used by default for query results + * + * @param integer $fetchmode DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC + * or DB_FETCHMODE_OBJECT + * @param string $object_class the class name of the object to be returned + * by the fetch methods when the + * DB_FETCHMODE_OBJECT mode is selected. + * If no class is specified by default a cast + * to object from the assoc array row will be + * done. There is also the posibility to use + * and extend the 'DB_row' class. + * + * @see DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT + */ + function setFetchMode($fetchmode, $object_class = 'stdClass') + { + switch ($fetchmode) { + case DB_FETCHMODE_OBJECT: + $this->fetchmode_object_class = $object_class; + case DB_FETCHMODE_ORDERED: + case DB_FETCHMODE_ASSOC: + $this->fetchmode = $fetchmode; + break; + default: + return $this->raiseError('invalid fetchmode mode'); + } + } + + // }}} + // {{{ setOption() + + /** + * Sets run-time configuration options for PEAR DB + * + * Options, their data types, default values and description: + *
    + *
  • + * autofree boolean = false + *
    should results be freed automatically when there are no + * more rows? + *
  • + * result_buffering integer = 500 + *
    how many rows of the result set should be buffered? + *
    In mysql: mysql_unbuffered_query() is used instead of + * mysql_query() if this value is 0. (Release 1.7.0) + *
    In oci8: this value is passed to ocisetprefetch(). + * (Release 1.7.0) + *
  • + * debug integer = 0 + *
    debug level + *
  • + * persistent boolean = false + *
    should the connection be persistent? + *
  • + * portability integer = DB_PORTABILITY_NONE + *
    portability mode constant (see below) + *
  • + * seqname_format string = %s_seq + *
    the sprintf() format string used on sequence names. This + * format is applied to sequence names passed to + * createSequence(), nextID() and dropSequence(). + *
  • + * ssl boolean = false + *
    use ssl to connect? + *
  • + *
+ * + * ----------------------------------------- + * + * PORTABILITY MODES + * + * These modes are bitwised, so they can be combined using | + * and removed using ^. See the examples section below on how + * to do this. + * + * DB_PORTABILITY_NONE + * turn off all portability features + * + * This mode gets automatically turned on if the deprecated + * optimize option gets set to performance. + * + * + * DB_PORTABILITY_LOWERCASE + * convert names of tables and fields to lower case when using + * get*(), fetch*() and tableInfo() + * + * This mode gets automatically turned on in the following databases + * if the deprecated option optimize gets set to + * portability: + * + oci8 + * + * + * DB_PORTABILITY_RTRIM + * right trim the data output by get*() fetch*() + * + * + * DB_PORTABILITY_DELETE_COUNT + * force reporting the number of rows deleted + * + * Some DBMS's don't count the number of rows deleted when performing + * simple DELETE FROM tablename queries. This portability + * mode tricks such DBMS's into telling the count by adding + * WHERE 1=1 to the end of DELETE queries. + * + * This mode gets automatically turned on in the following databases + * if the deprecated option optimize gets set to + * portability: + * + fbsql + * + mysql + * + mysqli + * + sqlite + * + * + * DB_PORTABILITY_NUMROWS + * enable hack that makes numRows() work in Oracle + * + * This mode gets automatically turned on in the following databases + * if the deprecated option optimize gets set to + * portability: + * + oci8 + * + * + * DB_PORTABILITY_ERRORS + * makes certain error messages in certain drivers compatible + * with those from other DBMS's + * + * + mysql, mysqli: change unique/primary key constraints + * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT + * + * + odbc(access): MS's ODBC driver reports 'no such field' as code + * 07001, which means 'too few parameters.' When this option is on + * that code gets mapped to DB_ERROR_NOSUCHFIELD. + * DB_ERROR_MISMATCH -> DB_ERROR_NOSUCHFIELD + * + * DB_PORTABILITY_NULL_TO_EMPTY + * convert null values to empty strings in data output by get*() and + * fetch*(). Needed because Oracle considers empty strings to be null, + * while most other DBMS's know the difference between empty and null. + * + * + * DB_PORTABILITY_ALL + * turn on all portability features + * + * ----------------------------------------- + * + * Example 1. Simple setOption() example + * + * $db->setOption('autofree', true); + * + * + * Example 2. Portability for lowercasing and trimming + * + * $db->setOption('portability', + * DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM); + * + * + * Example 3. All portability options except trimming + * + * $db->setOption('portability', + * DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM); + * + * + * @param string $option option name + * @param mixed $value value for the option + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::$options + */ + function setOption($option, $value) + { + if (isset($this->options[$option])) { + $this->options[$option] = $value; + + /* + * Backwards compatibility check for the deprecated 'optimize' + * option. Done here in case settings change after connecting. + */ + if ($option == 'optimize') { + if ($value == 'portability') { + switch ($this->phptype) { + case 'oci8': + $this->options['portability'] = + DB_PORTABILITY_LOWERCASE | + DB_PORTABILITY_NUMROWS; + break; + case 'fbsql': + case 'mysql': + case 'mysqli': + case 'sqlite': + $this->options['portability'] = + DB_PORTABILITY_DELETE_COUNT; + break; + } + } else { + $this->options['portability'] = DB_PORTABILITY_NONE; + } + } + + return DB_OK; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ getOption() + + /** + * Returns the value of an option + * + * @param string $option the option name you're curious about + * + * @return mixed the option's value + */ + function getOption($option) + { + if (isset($this->options[$option])) { + return $this->options[$option]; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute() + * + * Creates a query that can be run multiple times. Each time it is run, + * the placeholders, if any, will be replaced by the contents of + * execute()'s $data argument. + * + * Three types of placeholders can be used: + * + ? scalar value (i.e. strings, integers). The system + * will automatically quote and escape the data. + * + ! value is inserted 'as is' + * + & requires a file name. The file's contents get + * inserted into the query (i.e. saving binary + * data in a db) + * + * Example 1. + * + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res = $db->execute($sth, $data); + * + * + * Use backslashes to escape placeholder characters if you don't want + * them to be interpreted as placeholders: + *
+     *    "UPDATE foo SET col=? WHERE col='over \& under'"
+     * 
+ * + * With some database backends, this is emulated. + * + * {@internal ibase and oci8 have their own prepare() methods.}} + * + * @param string $query the query to be prepared + * + * @return mixed DB statement resource on success. A DB_Error object + * on failure. + * + * @see DB_common::execute() + */ + function prepare($query) + { + $tokens = preg_split('/((?prepare_tokens[] = &$newtokens; + end($this->prepare_tokens); + + $k = key($this->prepare_tokens); + $this->prepare_types[$k] = $types; + $this->prepared_queries[$k] = implode(' ', $newtokens); + + return $k; + } + + // }}} + // {{{ autoPrepare() + + /** + * Automaticaly generates an insert or update query and pass it to prepare() + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return resource the query handle + * + * @uses DB_common::prepare(), DB_common::buildManipSQL() + */ + function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT, + $where = false) + { + $query = $this->buildManipSQL($table, $table_fields, $mode, $where); + if (DB::isError($query)) { + return $query; + } + return $this->prepare($query); + } + + // }}} + // {{{ autoExecute() + + /** + * Automaticaly generates an insert or update query and call prepare() + * and execute() with it + * + * @param string $table the table name + * @param array $fields_values the associative array where $key is a + * field name and $value its value + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * @uses DB_common::autoPrepare(), DB_common::execute() + */ + function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT, + $where = false) + { + $sth = $this->autoPrepare($table, array_keys($fields_values), $mode, + $where); + if (DB::isError($sth)) { + return $sth; + } + $ret =& $this->execute($sth, array_values($fields_values)); + $this->freePrepared($sth); + return $ret; + + } + + // }}} + // {{{ buildManipSQL() + + /** + * Produces an SQL query string for autoPrepare() + * + * Example: + *
+     * buildManipSQL('table_sql', array('field1', 'field2', 'field3'),
+     *               DB_AUTOQUERY_INSERT);
+     * 
+ * + * That returns + * + * INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) + * + * + * NOTES: + * - This belongs more to a SQL Builder class, but this is a simple + * facility. + * - Be carefull! If you don't give a $where param with an UPDATE + * query, all the records of the table will be updated! + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return string the sql query for autoPrepare() + */ + function buildManipSQL($table, $table_fields, $mode, $where = false) + { + if (count($table_fields) == 0) { + return $this->raiseError(DB_ERROR_NEED_MORE_DATA); + } + $first = true; + switch ($mode) { + case DB_AUTOQUERY_INSERT: + $values = ''; + $names = ''; + foreach ($table_fields as $value) { + if ($first) { + $first = false; + } else { + $names .= ','; + $values .= ','; + } + $names .= $value; + $values .= '?'; + } + return "INSERT INTO $table ($names) VALUES ($values)"; + case DB_AUTOQUERY_UPDATE: + $set = ''; + foreach ($table_fields as $value) { + if ($first) { + $first = false; + } else { + $set .= ','; + } + $set .= "$value = ?"; + } + $sql = "UPDATE $table SET $set"; + if ($where) { + $sql .= " WHERE $where"; + } + return $sql; + default: + return $this->raiseError(DB_ERROR_SYNTAX); + } + } + + // }}} + // {{{ execute() + + /** + * Executes a DB statement prepared with prepare() + * + * Example 1. + * + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res =& $db->execute($sth, $data); + * + * + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * {@internal ibase and oci8 have their own execute() methods.}} + * + * @see DB_common::prepare() + */ + function &execute($stmt, $data = array()) + { + $realquery = $this->executeEmulateQuery($stmt, $data); + if (DB::isError($realquery)) { + return $realquery; + } + $result = $this->simpleQuery($realquery); + + if ($result === DB_OK || DB::isError($result)) { + return $result; + } else { + $tmp =& new DB_result($this, $result); + return $tmp; + } + } + + // }}} + // {{{ executeEmulateQuery() + + /** + * Emulates executing prepared statements if the DBMS not support them + * + * @param resource $stmt a DB statement resource returned from execute() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a string containing the real query run when emulating + * prepare/execute. A DB_Error object on failure. + * + * @access protected + * @see DB_common::execute() + */ + function executeEmulateQuery($stmt, $data = array()) + { + $stmt = (int)$stmt; + $data = (array)$data; + $this->last_parameters = $data; + + if (count($this->prepare_types[$stmt]) != count($data)) { + $this->last_query = $this->prepared_queries[$stmt]; + return $this->raiseError(DB_ERROR_MISMATCH); + } + + $realquery = $this->prepare_tokens[$stmt][0]; + + $i = 0; + foreach ($data as $value) { + if ($this->prepare_types[$stmt][$i] == DB_PARAM_SCALAR) { + $realquery .= $this->quoteSmart($value); + } elseif ($this->prepare_types[$stmt][$i] == DB_PARAM_OPAQUE) { + $fp = @fopen($value, 'rb'); + if (!$fp) { + return $this->raiseError(DB_ERROR_ACCESS_VIOLATION); + } + $realquery .= $this->quoteSmart(fread($fp, filesize($value))); + fclose($fp); + } else { + $realquery .= $value; + } + + $realquery .= $this->prepare_tokens[$stmt][++$i]; + } + + return $realquery; + } + + // }}} + // {{{ executeMultiple() + + /** + * Performs several execute() calls on the same statement handle + * + * $data must be an array indexed numerically + * from 0, one execute call is done for every "row" in the array. + * + * If an error occurs during execute(), executeMultiple() does not + * execute the unfinished rows, but rather returns that error. + * + * @param resource $stmt query handle from prepare() + * @param array $data numeric array containing the + * data to insert into the query + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::prepare(), DB_common::execute() + */ + function executeMultiple($stmt, $data) + { + foreach ($data as $value) { + $res =& $this->execute($stmt, $value); + if (DB::isError($res)) { + return $res; + } + } + return DB_OK; + } + + // }}} + // {{{ freePrepared() + + /** + * Frees the internal resources associated with a prepared query + * + * @param resource $stmt the prepared statement's PHP resource + * @param bool $free_resource should the PHP resource be freed too? + * Use false if you need to get data + * from the result set later. + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_common::prepare() + */ + function freePrepared($stmt, $free_resource = true) + { + $stmt = (int)$stmt; + if (isset($this->prepare_tokens[$stmt])) { + unset($this->prepare_tokens[$stmt]); + unset($this->prepare_types[$stmt]); + unset($this->prepared_queries[$stmt]); + return true; + } + return false; + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * It is defined here to ensure all drivers have this method available. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_mysql::modifyQuery(), DB_oci8::modifyQuery(), + * DB_sqlite::modifyQuery() + */ + function modifyQuery($query) + { + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * It is defined here to assure that all implementations + * have this method defined. + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return $query; + } + + // }}} + // {{{ query() + + /** + * Sends a query to the database server + * + * The query string can be either a normal statement to be sent directly + * to the server OR if $params are passed the query can have + * placeholders and it will be passed through prepare() and execute(). + * + * @param string $query the SQL query or the statement to prepare + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * @see DB_result, DB_common::prepare(), DB_common::execute() + */ + function &query($query, $params = array()) + { + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $ret =& $this->execute($sth, $params); + $this->freePrepared($sth, false); + return $ret; + } else { + $this->last_parameters = array(); + $result = $this->simpleQuery($query); + if ($result === DB_OK || DB::isError($result)) { + return $result; + } else { + $tmp =& new DB_result($this, $result); + return $tmp; + } + } + } + + // }}} + // {{{ limitQuery() + + /** + * Generates and executes a LIMIT query + * + * @param string $query the query + * @param intr $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + */ + function &limitQuery($query, $from, $count, $params = array()) + { + $query = $this->modifyLimitQuery($query, $from, $count, $params); + if (DB::isError($query)){ + return $query; + } + $result =& $this->query($query, $params); + if (is_a($result, 'DB_result')) { + $result->setOption('limit_from', $from); + $result->setOption('limit_count', $count); + } + return $result; + } + + // }}} + // {{{ getOne() + + /** + * Fetches the first column of the first row from a query result + * + * Takes care of doing the query and freeing the results when finished. + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed the returned value of the query. + * A DB_Error object on failure. + */ + function &getOne($query, $params = array()) + { + $params = (array)$params; + // modifyLimitQuery() would be nice here, but it causes BC issues + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED); + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row[0]; + } + + // }}} + // {{{ getRow() + + /** + * Fetches the first row of data returned from a query result + * + * Takes care of doing the query and freeing the results when finished. + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use + * + * @return array the first row of results as an array. + * A DB_Error object on failure. + */ + function &getRow($query, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + if ($params === null) { + $tmp = DB_FETCHMODE_DEFAULT; + } else { + $tmp = $params; + } + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = array(); + } + } + // modifyLimitQuery() would be nice here, but it causes BC issues + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, $fetchmode); + + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row; + } + + // }}} + // {{{ getCol() + + /** + * Fetches a single column from a query result and returns it as an + * indexed array + * + * @param string $query the SQL query + * @param mixed $col which column to return (integer [column number, + * starting at 0] or string [column name]) + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return array the results as an array. A DB_Error object on failure. + * + * @see DB_common::query() + */ + function &getCol($query, $col = 0, $params = array()) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC; + + if (!is_array($row = $res->fetchRow($fetchmode))) { + $ret = array(); + } else { + if (!array_key_exists($col, $row)) { + $ret =& $this->raiseError(DB_ERROR_NOSUCHFIELD); + } else { + $ret = array($row[$col]); + while (is_array($row = $res->fetchRow($fetchmode))) { + $ret[] = $row[$col]; + } + } + } + + $res->free(); + + if (DB::isError($row)) { + $ret = $row; + } + + return $ret; + } + + // }}} + // {{{ getAssoc() + + /** + * Fetches an entire query result and returns it as an + * associative array using the first column as the key + * + * If the result set contains more than two columns, the value + * will be an array of the values from column 2-n. If the result + * set contains only two columns, the returned value will be a + * scalar with the value of the second column (unless forced to an + * array with the $force_array parameter). A DB error code is + * returned on errors. If the result set contains fewer than two + * columns, a DB_ERROR_TRUNCATED error is returned. + * + * For example, if the table "mytable" contains: + * + *
+     *  ID      TEXT       DATE
+     * --------------------------------
+     *  1       'one'      944679408
+     *  2       'two'      944679408
+     *  3       'three'    944679408
+     * 
+ * + * Then the call getAssoc('SELECT id,text FROM mytable') returns: + *
+     *   array(
+     *     '1' => 'one',
+     *     '2' => 'two',
+     *     '3' => 'three',
+     *   )
+     * 
+ * + * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: + *
+     *   array(
+     *     '1' => array('one', '944679408'),
+     *     '2' => array('two', '944679408'),
+     *     '3' => array('three', '944679408')
+     *   )
+     * 
+ * + * If the more than one row occurs with the same value in the + * first column, the last row overwrites all previous ones by + * default. Use the $group parameter if you don't want to + * overwrite like this. Example: + * + *
+     * getAssoc('SELECT category,id,name FROM mytable', false, null,
+     *          DB_FETCHMODE_ASSOC, true) returns:
+     *
+     *   array(
+     *     '1' => array(array('id' => '4', 'name' => 'number four'),
+     *                  array('id' => '6', 'name' => 'number six')
+     *            ),
+     *     '9' => array(array('id' => '4', 'name' => 'number four'),
+     *                  array('id' => '6', 'name' => 'number six')
+     *            )
+     *   )
+     * 
+ * + * Keep in mind that database functions in PHP usually return string + * values for results regardless of the database's internal type. + * + * @param string $query the SQL query + * @param bool $force_array used only when the query returns + * exactly two columns. If true, the values + * of the returned array will be one-element + * arrays instead of scalars. + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of + * items passed must match quantity of + * placeholders in query: meaning 1 + * placeholder for non-array parameters or + * 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use + * @param bool $group if true, the values of the returned array + * is wrapped in another array. If the same + * key value (in the first column) repeats + * itself, the values will be appended to + * this array instead of overwriting the + * existing values. + * + * @return array the associative array containing the query results. + * A DB_Error object on failure. + */ + function &getAssoc($query, $force_array = false, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT, $group = false) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + if ($fetchmode == DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + $cols = $res->numCols(); + + if ($cols < 2) { + $tmp =& $this->raiseError(DB_ERROR_TRUNCATED); + return $tmp; + } + + $results = array(); + + if ($cols > 2 || $force_array) { + // return array values + // XXX this part can be optimized + if ($fetchmode == DB_FETCHMODE_ASSOC) { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) { + reset($row); + $key = current($row); + unset($row[key($row)]); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } elseif ($fetchmode == DB_FETCHMODE_OBJECT) { + while ($row = $res->fetchRow(DB_FETCHMODE_OBJECT)) { + $arr = get_object_vars($row); + $key = current($arr); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } else { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + // we shift away the first element to get + // indices running from 0 again + $key = array_shift($row); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } + if (DB::isError($row)) { + $results = $row; + } + } else { + // return scalar values + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + if ($group) { + $results[$row[0]][] = $row[1]; + } else { + $results[$row[0]] = $row[1]; + } + } + if (DB::isError($row)) { + $results = $row; + } + } + + $res->free(); + + return $results; + } + + // }}} + // {{{ getAll() + + /** + * Fetches all of the rows from a query result + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of + * items passed must match quantity of + * placeholders in query: meaning 1 + * placeholder for non-array parameters or + * 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use: + * + DB_FETCHMODE_ORDERED + * + DB_FETCHMODE_ASSOC + * + DB_FETCHMODE_ORDERED | DB_FETCHMODE_FLIPPED + * + DB_FETCHMODE_ASSOC | DB_FETCHMODE_FLIPPED + * + * @return array the nested array. A DB_Error object on failure. + */ + function &getAll($query, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + if ($params === null) { + $tmp = DB_FETCHMODE_DEFAULT; + } else { + $tmp = $params; + } + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = array(); + } + } + + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if ($res === DB_OK || DB::isError($res)) { + return $res; + } + + $results = array(); + while (DB_OK === $res->fetchInto($row, $fetchmode)) { + if ($fetchmode & DB_FETCHMODE_FLIPPED) { + foreach ($row as $key => $val) { + $results[$key][] = $val; + } + } else { + $results[] = $row; + } + } + + $res->free(); + + if (DB::isError($row)) { + $tmp =& $this->raiseError($row); + return $tmp; + } + return $results; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ numRows() + + /** + * Determines the number of rows in a query result + * + * @param resource $result the query result idenifier produced by PHP + * + * @return int the number of rows. A DB_Error object on failure. + */ + function numRows($result) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getSequenceName() + + /** + * Generates the name used inside the database for a sequence + * + * The createSequence() docblock contains notes about storing sequence + * names. + * + * @param string $sqn the sequence's public name + * + * @return string the sequence's name in the backend + * + * @access protected + * @see DB_common::createSequence(), DB_common::dropSequence(), + * DB_common::nextID(), DB_common::setOption() + */ + function getSequenceName($sqn) + { + return sprintf($this->getOption('seqname_format'), + preg_replace('/[^a-z0-9_.]/i', '_', $sqn)); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::dropSequence(), + * DB_common::getSequenceName() + */ + function nextId($seq_name, $ondemand = true) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * The name of a given sequence is determined by passing the string + * provided in the $seq_name argument through PHP's sprintf() + * function using the value from the seqname_format option as + * the sprintf()'s format argument. + * + * seqname_format is set via setOption(). + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_common::nextID() + */ + function createSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_common::nextID() + */ + function dropSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ raiseError() + + /** + * Communicates an error and invoke error callbacks, etc + * + * Basically a wrapper for PEAR::raiseError without the message string. + * + * @param mixed integer error code, or a PEAR error object (all + * other parameters are ignored if this parameter is + * an object + * @param int error mode, see PEAR_Error docs + * @param mixed if error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * @param string extra debug information. Defaults to the last + * query and native error code. + * @param mixed native error code, integer or string depending the + * backend + * + * @return object the PEAR_Error object + * + * @see PEAR_Error + */ + function &raiseError($code = DB_ERROR, $mode = null, $options = null, + $userinfo = null, $nativecode = null) + { + // The error is yet a DB error object + if (is_object($code)) { + // because we the static PEAR::raiseError, our global + // handler should be used if it is set + if ($mode === null && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + $tmp = PEAR::raiseError($code, null, $mode, $options, + null, null, true); + return $tmp; + } + + if ($userinfo === null) { + $userinfo = $this->last_query; + } + + if ($nativecode) { + $userinfo .= ' [nativecode=' . trim($nativecode) . ']'; + } else { + $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']'; + } + + $tmp = PEAR::raiseError(null, $code, $mode, $options, $userinfo, + 'DB_Error', true); + return $tmp; + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return mixed the DBMS' error code. A DB_Error object on failure. + */ + function errorNative() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ errorCode() + + /** + * Maps native error codes to DB's portable ones + * + * Uses the $errorcode_map property defined in each driver. + * + * @param string|int $nativecode the error code returned by the DBMS + * + * @return int the portable DB error code. Return DB_ERROR if the + * current driver doesn't have a mapping for the + * $nativecode submitted. + */ + function errorCode($nativecode) + { + if (isset($this->errorcode_map[$nativecode])) { + return $this->errorcode_map[$nativecode]; + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ errorMessage() + + /** + * Maps a DB error code to a textual message + * + * @param integer $dbcode the DB error code + * + * @return string the error message corresponding to the error code + * submitted. FALSE if the error code is unknown. + * + * @see DB::errorMessage() + */ + function errorMessage($dbcode) + { + return DB::errorMessage($this->errorcode_map[$dbcode]); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * The format of the resulting array depends on which $mode + * you select. The sample output below is based on this query: + *
+     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+     *    FROM tblFoo
+     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+     * 
+ * + *
    + *
  • + * + * null (default) + *
    +     *   [0] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   [1] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldPhone
    +     *       [type] => string
    +     *       [len] => 20
    +     *       [flags] =>
    +     *   )
    +     *   [2] => Array (
    +     *       [table] => tblBar
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   
    + * + *
  • + * + * DB_TABLEINFO_ORDER + * + *

    In addition to the information found in the default output, + * a notation of the number of columns is provided by the + * num_fields element while the order + * element provides an array with the column names as the keys and + * their location index number (corresponding to the keys in the + * the default output) as the values.

    + * + *

    If a result set has identical field names, the last one is + * used.

    + * + *
    +     *   [num_fields] => 3
    +     *   [order] => Array (
    +     *       [fldId] => 2
    +     *       [fldTrans] => 1
    +     *   )
    +     *   
    + * + *
  • + * + * DB_TABLEINFO_ORDERTABLE + * + *

    Similar to DB_TABLEINFO_ORDER but adds more + * dimensions to the array in which the table names are keys and + * the field names are sub-keys. This is helpful for queries that + * join tables which have identical field names.

    + * + *
    +     *   [num_fields] => 3
    +     *   [ordertable] => Array (
    +     *       [tblFoo] => Array (
    +     *           [fldId] => 0
    +     *           [fldPhone] => 1
    +     *       )
    +     *       [tblBar] => Array (
    +     *           [fldId] => 2
    +     *       )
    +     *   )
    +     *   
    + * + *
  • + *
+ * + * The flags element contains a space separated list + * of extra information about the field. This data is inconsistent + * between DBMS's due to the way each DBMS works. + * + primary_key + * + unique_key + * + multiple_key + * + not_null + * + * Most DBMS's only provide the table and flags + * elements if $result is a table name. The following DBMS's + * provide full information from queries: + * + fbsql + * + mysql + * + * If the 'portability' option has DB_PORTABILITY_LOWERCASE + * turned on, the names of tables and fields will be lowercased. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode either unused or one of the tableInfo modes: + * DB_TABLEINFO_ORDERTABLE, + * DB_TABLEINFO_ORDER or + * DB_TABLEINFO_FULL (which does both). + * These are bitwise, so the first two can be + * combined using |. + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function tableInfo($result, $mode = null) + { + /* + * If the DB_ class has a tableInfo() method, that one + * overrides this one. But, if the driver doesn't have one, + * this method runs and tells users about that fact. + */ + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getTables() + + /** + * Lists the tables in the current database + * + * @return array the list of tables. A DB_Error object on failure. + * + * @deprecated Method deprecated some time before Release 1.2 + */ + function getTables() + { + return $this->getListOf('tables'); + } + + // }}} + // {{{ getListOf() + + /** + * Lists internal database information + * + * @param string $type type of information being sought. + * Common items being sought are: + * tables, databases, users, views, functions + * Each DBMS's has its own capabilities. + * + * @return array an array listing the items sought. + * A DB DB_Error object on failure. + */ + function getListOf($type) + { + $sql = $this->getSpecialQuery($type); + if ($sql === null) { + $this->last_query = ''; + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } elseif (is_int($sql) || DB::isError($sql)) { + // Previous error + return $this->raiseError($sql); + } elseif (is_array($sql)) { + // Already the result + return $sql; + } + // Launch this query + return $this->getCol($sql); + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } + + // }}} + // {{{ _rtrimArrayValues() + + /** + * Right-trims all strings in an array + * + * @param array $array the array to be trimmed (passed by reference) + * + * @return void + * + * @access protected + */ + function _rtrimArrayValues(&$array) + { + foreach ($array as $key => $value) { + if (is_string($value)) { + $array[$key] = rtrim($value); + } + } + } + + // }}} + // {{{ _convertNullArrayValuesToEmpty() + + /** + * Converts all null values in an array to empty strings + * + * @param array $array the array to be de-nullified (passed by reference) + * + * @return void + * + * @access protected + */ + function _convertNullArrayValuesToEmpty(&$array) + { + foreach ($array as $key => $value) { + if (is_null($value)) { + $array[$key] = ''; + } + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/include/PEAR/DB/mysql.php b/include/PEAR/DB/mysql.php new file mode 100644 index 0000000..3ae0adf --- /dev/null +++ b/include/PEAR/DB/mysql.php @@ -0,0 +1,1034 @@ + + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: mysql.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's mysql extension + * for interacting with MySQL databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Stig Bakken + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_mysql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'mysql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'mysql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '4.2.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 1004 => DB_ERROR_CANNOT_CREATE, + 1005 => DB_ERROR_CANNOT_CREATE, + 1006 => DB_ERROR_CANNOT_CREATE, + 1007 => DB_ERROR_ALREADY_EXISTS, + 1008 => DB_ERROR_CANNOT_DROP, + 1022 => DB_ERROR_ALREADY_EXISTS, + 1044 => DB_ERROR_ACCESS_VIOLATION, + 1046 => DB_ERROR_NODBSELECTED, + 1048 => DB_ERROR_CONSTRAINT, + 1049 => DB_ERROR_NOSUCHDB, + 1050 => DB_ERROR_ALREADY_EXISTS, + 1051 => DB_ERROR_NOSUCHTABLE, + 1054 => DB_ERROR_NOSUCHFIELD, + 1061 => DB_ERROR_ALREADY_EXISTS, + 1062 => DB_ERROR_ALREADY_EXISTS, + 1064 => DB_ERROR_SYNTAX, + 1091 => DB_ERROR_NOT_FOUND, + 1100 => DB_ERROR_NOT_LOCKED, + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, + 1142 => DB_ERROR_ACCESS_VIOLATION, + 1146 => DB_ERROR_NOSUCHTABLE, + 1216 => DB_ERROR_CONSTRAINT, + 1217 => DB_ERROR_CONSTRAINT, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = ''; + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_mysql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's mysql driver supports the following extra DSN options: + * + new_link If set to true, causes subsequent calls to connect() + * to return a new connection link instead of the + * existing one. WARNING: this is not portable to + * other DBMS's. Available since PEAR DB 1.7.0. + * + client_flags Any combination of MYSQL_CLIENT_* constants. + * Only used if PHP is at version 4.3.0 or greater. + * Available since PEAR DB 1.7.0. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('mysql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array(); + if ($dsn['protocol'] && $dsn['protocol'] == 'unix') { + $params[0] = ':' . $dsn['socket']; + } else { + $params[0] = $dsn['hostspec'] ? $dsn['hostspec'] + : 'localhost'; + if ($dsn['port']) { + $params[0] .= ':' . $dsn['port']; + } + } + $params[] = $dsn['username'] ? $dsn['username'] : null; + $params[] = $dsn['password'] ? $dsn['password'] : null; + + if (!$persistent) { + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + $params[] = true; + } else { + $params[] = false; + } + } + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = isset($dsn['client_flags']) + ? $dsn['client_flags'] : null; + } + + $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + ini_set('track_errors', $ini); + } + + if (!$this->connection) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $err); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + + if ($dsn['database']) { + if (!@mysql_select_db($dsn['database'], $this->connection)) { + return $this->mysqlRaiseError(); + } + $this->_db = $dsn['database']; + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @mysql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * Generally uses mysql_query(). If you want to use + * mysql_unbuffered_query() set the "result_buffering" option to 0 using + * setOptions(). This option was added in Release 1.7.0. + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = DB::isManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection); + $result = @mysql_query('BEGIN', $this->connection); + if (!$result) { + return $this->mysqlRaiseError(); + } + } + $this->transaction_opcount++; + } + if (!$this->options['result_buffering']) { + $result = @mysql_unbuffered_query($query, $this->connection); + } else { + $result = @mysql_query($query, $this->connection); + } + if (!$result) { + return $this->mysqlRaiseError(); + } + if (is_resource($result)) { + return $result; + } + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal mysql result pointer to the next available result + * + * This method has not been implemented yet. + * + * @param a valid sql result resource + * + * @return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@mysql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @mysql_fetch_array($result, MYSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @mysql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + /* + * Even though this DBMS already trims output, we do this because + * a field might have intentional whitespace at the end that + * gets removed by DB_PORTABILITY_RTRIM under another driver. + */ + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return @mysql_free_result($result); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @mysql_num_fields($result); + if (!$cols) { + return $this->mysqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @mysql_num_rows($result); + if ($rows === null) { + return $this->mysqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysql_query('COMMIT', $this->connection); + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysql_query('ROLLBACK', $this->connection); + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if (DB::isManip($this->last_query)) { + return @mysql_affected_rows($this->connection); + } else { + return 0; + } + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_mysql::createSequence(), DB_mysql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("UPDATE ${seqname} ". + 'SET id=LAST_INSERT_ID(id+1)'); + $this->popErrorHandling(); + if ($result === DB_OK) { + // COMMON CASE + $id = @mysql_insert_id($this->connection); + if ($id != 0) { + return $id; + } + // EMPTY SEQ TABLE + // Sequence table must be empty for some reason, so fill + // it and return 1 and obtain a user-level lock + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + if ($result == 0) { + // Failed to get the lock + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + // add the default value + $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + + // Release the lock + $result = $this->getOne('SELECT RELEASE_LOCK(' + . "'${seqname}_lock')"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + // We know what the result will be, so no need to try again + return 1; + + } elseif ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) + { + // ONDEMAND TABLE CREATION + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } else { + $repeat = 1; + } + + } elseif (DB::isError($result) && + $result->getCode() == DB_ERROR_ALREADY_EXISTS) + { + // BACKWARDS COMPAT + // see _BCsequence() comment + $result = $this->_BCsequence($seqname); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $repeat = 1; + } + } while ($repeat); + + return $this->raiseError($result); + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' + . ' PRIMARY KEY(id))'); + if (DB::isError($res)) { + return $res; + } + // insert yields value 1, nextId call will generate ID 2 + $res = $this->query("INSERT INTO ${seqname} (id) VALUES (0)"); + if (DB::isError($res)) { + return $res; + } + // so reset to zero + return $this->query("UPDATE ${seqname} SET id = 0"); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ _BCsequence() + + /** + * Backwards compatibility with old sequence emulation implementation + * (clean up the dupes) + * + * @param string $seqname the sequence name to clean up + * + * @return bool true on success. A DB_Error object on failure. + * + * @access private + */ + function _BCsequence($seqname) + { + // Obtain a user-level lock... this will release any previous + // application locks, but unlike LOCK TABLES, it does not abort + // the current transaction and is much less frequently used. + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $result; + } + if ($result == 0) { + // Failed to get the lock, can't do the conversion, bail + // with a DB_ERROR_NOT_LOCKED error + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}"); + if (DB::isError($highest_id)) { + return $highest_id; + } + // This should kill all rows except the highest + // We should probably do something if $highest_id isn't + // numeric, but I'm at a loss as how to handle that... + $result = $this->query('DELETE FROM ' . $seqname + . " WHERE id <> $highest_id"); + if (DB::isError($result)) { + return $result; + } + + // If another thread has been waiting for this lock, + // it will go thru the above procedure, but will have no + // real effect + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')"); + if (DB::isError($result)) { + return $result; + } + return true; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * MySQL can't handle the backtick character (`) in + * table or column names. + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '`' . $str . '`'; + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + if (function_exists('mysql_real_escape_string')) { + return @mysql_real_escape_string($str, $this->connection); + } else { + return @mysql_escape_string($str); + } + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * This little hack lets you know how many rows were deleted + * when running a "DELETE FROM table" query. Only implemented + * if the DB_PORTABILITY_DELETE_COUNT portability option is on. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_common::setOption() + */ + function modifyQuery($query) + { + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if (DB::isManip($query)) { + return $query . " LIMIT $count"; + } else { + return $query . " LIMIT $from, $count"; + } + } + + // }}} + // {{{ mysqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mysql::errorNative(), DB_common::errorCode() + */ + function mysqlRaiseError($errno = null) + { + if ($errno === null) { + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; + } + $errno = $this->errorCode(mysql_errno($this->connection)); + } + return $this->raiseError($errno, null, null, null, + @mysql_errno($this->connection) . ' ** ' . + @mysql_error($this->connection)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + return @mysql_errno($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @mysql_list_fields($this->dsn['database'], + $result, $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @mysql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@mysql_field_table($id, $i)), + 'name' => $case_func(@mysql_field_name($id, $i)), + 'type' => @mysql_field_type($id, $i), + 'len' => @mysql_field_len($id, $i), + 'flags' => @mysql_field_flags($id, $i), + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @mysql_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SHOW TABLES'; + case 'users': + return 'SELECT DISTINCT User FROM mysql.user'; + case 'databases': + return 'SHOW DATABASES'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/include/PEAR/DB/pgsql.php b/include/PEAR/DB/pgsql.php new file mode 100644 index 0000000..1e58f48 --- /dev/null +++ b/include/PEAR/DB/pgsql.php @@ -0,0 +1,1097 @@ + + * @author Stig Bakken + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: pgsql.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's pgsql extension + * for interacting with PostgreSQL databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Rui Hirokawa + * @author Stig Bakken + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_pgsql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'pgsql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'pgsql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '4.3.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => true, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The number of rows affected by a data manipulation query + * @var integer + */ + var $affected = 0; + + /** + * The current row being looked at in fetchInto() + * @var array + * @access private + */ + var $row = array(); + + /** + * The number of rows in a given result set + * @var array + * @access private + */ + var $_num_rows = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_pgsql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's pgsql driver supports the following extra DSN options: + * + connect_timeout How many seconds to wait for a connection to + * be established. Available since PEAR DB 1.7.0. + * + new_link If set to true, causes subsequent calls to + * connect() to return a new connection link + * instead of the existing one. WARNING: this is + * not portable to other DBMS's. Available only + * if PHP is >= 4.3.0 and PEAR DB is >= 1.7.0. + * + options Command line options to be sent to the server. + * Available since PEAR DB 1.6.4. + * + service Specifies a service name in pg_service.conf that + * holds additional connection parameters. + * Available since PEAR DB 1.7.0. + * + sslmode How should SSL be used when connecting? Values: + * disable, allow, prefer or require. + * Available since PEAR DB 1.7.0. + * + tty This was used to specify where to send server + * debug output. Available since PEAR DB 1.6.4. + * + * Example of connecting to a new link via a socket: + * + * require_once 'DB.php'; + * + * $dsn = 'pgsql://user:pass@unix(/tmp)/dbname?new_link=true'; + * $options = array( + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db =& DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('pgsql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $protocol = $dsn['protocol'] ? $dsn['protocol'] : 'tcp'; + + $params = array(''); + if ($protocol == 'tcp') { + if ($dsn['hostspec']) { + $params[0] .= 'host=' . $dsn['hostspec']; + } + if ($dsn['port']) { + $params[0] .= ' port=' . $dsn['port']; + } + } elseif ($protocol == 'unix') { + // Allow for pg socket in non-standard locations. + if ($dsn['socket']) { + $params[0] .= 'host=' . $dsn['socket']; + } + if ($dsn['port']) { + $params[0] .= ' port=' . $dsn['port']; + } + } + if ($dsn['database']) { + $params[0] .= ' dbname=\'' . addslashes($dsn['database']) . '\''; + } + if ($dsn['username']) { + $params[0] .= ' user=\'' . addslashes($dsn['username']) . '\''; + } + if ($dsn['password']) { + $params[0] .= ' password=\'' . addslashes($dsn['password']) . '\''; + } + if (!empty($dsn['options'])) { + $params[0] .= ' options=' . $dsn['options']; + } + if (!empty($dsn['tty'])) { + $params[0] .= ' tty=' . $dsn['tty']; + } + if (!empty($dsn['connect_timeout'])) { + $params[0] .= ' connect_timeout=' . $dsn['connect_timeout']; + } + if (!empty($dsn['sslmode'])) { + $params[0] .= ' sslmode=' . $dsn['sslmode']; + } + if (!empty($dsn['service'])) { + $params[0] .= ' service=' . $dsn['service']; + } + + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = PGSQL_CONNECT_FORCE_NEW; + } + } + + $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + ini_set('track_errors', $ini); + } + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @pg_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = DB::isManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @pg_exec($this->connection, 'begin;'); + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @pg_exec($this->connection, $query); + if (!$result) { + return $this->pgsqlRaiseError(); + } + // Determine which queries that should return data, and which + // should return an error code only. + if ($ismanip) { + $this->affected = @pg_affected_rows($result); + return DB_OK; + } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|SHOW)\s/si', $query)) { + /* PostgreSQL commands: + ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY, + CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH, + GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET, + REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW, + UNLISTEN, UPDATE, VACUUM + */ + $this->row[(int)$result] = 0; // reset the row counter. + $numrows = $this->numRows($result); + if (is_object($numrows)) { + return $numrows; + } + $this->_num_rows[(int)$result] = $numrows; + $this->affected = 0; + return $result; + } else { + $this->affected = 0; + return DB_OK; + } + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal pgsql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + $result_int = (int)$result; + $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int]; + if ($rownum >= $this->_num_rows[$result_int]) { + return null; + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @pg_fetch_row($result, $rownum); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + $this->row[$result_int] = ++$rownum; + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + if (is_resource($result)) { + unset($this->row[(int)$result]); + unset($this->_num_rows[(int)$result]); + $this->affected = 0; + return @pg_freeresult($result); + } + return false; + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + * @internal + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ quoteSmart() + + /** + * Formats input so it can be safely used in a query + * + * @param mixed $in the data to be formatted + * + * @return mixed the formatted data. The format depends on the input's + * PHP type: + * + null = the string NULL + * + boolean = string TRUE or FALSE + * + integer or double = the unquoted number + * + other (including strings and numeric strings) = + * the data escaped according to MySQL's settings + * then encapsulated between single quotes + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function quoteSmart($in) + { + if (is_int($in) || is_double($in)) { + return $in; + } elseif (is_bool($in)) { + return $in ? 'TRUE' : 'FALSE'; + } elseif (is_null($in)) { + return 'NULL'; + } else { + return "'" . $this->escapeSimple($in) . "'"; + } + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * {@internal PostgreSQL treats a backslash as an escape character, + * so they are escaped as well. + * + * Not using pg_escape_string() yet because it requires PostgreSQL + * to be at version 7.2 or greater.}} + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + return str_replace("'", "''", str_replace('\\', '\\\\', $str)); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @pg_numfields($result); + if (!$cols) { + return $this->pgsqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @pg_numrows($result); + if ($rows === null) { + return $this->pgsqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + // (disabled) hack to shut up error messages from libpq.a + //@fclose(@fopen("php://stderr", "w")); + $result = @pg_exec($this->connection, 'end;'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + $result = @pg_exec($this->connection, 'abort;'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return $this->affected; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_pgsql::createSequence(), DB_pgsql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = false; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result =& $this->query("SELECT NEXTVAL('${seqname}')"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = true; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->createSequence($seq_name); + $this->popErrorHandling(); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + $result->free(); + return $arr[0]; + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_pgsql::nextID(), DB_pgsql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $result = $this->query("CREATE SEQUENCE ${seqname}"); + return $result; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_pgsql::nextID(), DB_pgsql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP SEQUENCE ' + . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return "$query LIMIT $count OFFSET $from"; + } + + // }}} + // {{{ pgsqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_pgsql::errorNative(), DB_pgsql::errorCode() + */ + function pgsqlRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * {@internal Error messages are used instead of error codes + * in order to support older versions of PostgreSQL.}} + * + * @return string the DBMS' error message + */ + function errorNative() + { + return @pg_errormessage($this->connection); + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message. + * + * @param string $errormsg error message returned from the database + * @return integer an error number from a DB error constant + */ + function errorCode($errormsg) + { + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/(relation|sequence|table).*does not exist|class .* not found/i' + => DB_ERROR_NOSUCHTABLE, + '/index .* does not exist/' + => DB_ERROR_NOT_FOUND, + '/column .* does not exist/i' + => DB_ERROR_NOSUCHFIELD, + '/relation .* already exists/i' + => DB_ERROR_ALREADY_EXISTS, + '/(divide|division) by zero$/i' + => DB_ERROR_DIVZERO, + '/pg_atoi: error in .*: can\'t parse /i' + => DB_ERROR_INVALID_NUMBER, + '/invalid input syntax for( type)? (integer|numeric)/i' + => DB_ERROR_INVALID_NUMBER, + '/value .* is out of range for type \w*int/i' + => DB_ERROR_INVALID_NUMBER, + '/integer out of range/i' + => DB_ERROR_INVALID_NUMBER, + '/value too long for type character/i' + => DB_ERROR_INVALID, + '/attribute .* not found|relation .* does not have attribute/i' + => DB_ERROR_NOSUCHFIELD, + '/column .* specified in USING clause does not exist in (left|right) table/i' + => DB_ERROR_NOSUCHFIELD, + '/parser: parse error at or near/i' + => DB_ERROR_SYNTAX, + '/syntax error at/' + => DB_ERROR_SYNTAX, + '/column reference .* is ambiguous/i' + => DB_ERROR_SYNTAX, + '/permission denied/' + => DB_ERROR_ACCESS_VIOLATION, + '/violates not-null constraint/' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/violates [\w ]+ constraint/' + => DB_ERROR_CONSTRAINT, + '/referential integrity violation/' + => DB_ERROR_CONSTRAINT, + '/more expressions than target columns/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0"); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @pg_numfields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func(@pg_fieldname($id, $i)), + 'type' => @pg_fieldtype($id, $i), + 'len' => @pg_fieldsize($id, $i), + 'flags' => $got_string + ? $this->_pgFieldFlags($id, $i, $result) + : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @pg_freeresult($id); + } + return $res; + } + + // }}} + // {{{ _pgFieldFlags() + + /** + * Get a column's flags + * + * Supports "not_null", "default_value", "primary_key", "unique_key" + * and "multiple_key". The default value is passed through + * rawurlencode() in case there are spaces in it. + * + * @param int $resource the PostgreSQL result identifier + * @param int $num_field the field number + * + * @return string the flags + * + * @access private + */ + function _pgFieldFlags($resource, $num_field, $table_name) + { + $field_name = @pg_fieldname($resource, $num_field); + + $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef + FROM pg_attribute f, pg_class tab, pg_type typ + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attname = '$field_name' + AND tab.relname = '$table_name'"); + if (@pg_numrows($result) > 0) { + $row = @pg_fetch_row($result, 0); + $flags = ($row[0] == 't') ? 'not_null ' : ''; + + if ($row[1] == 't') { + $result = @pg_exec($this->connection, "SELECT a.adsrc + FROM pg_attribute f, pg_class tab, pg_type typ, pg_attrdef a + WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid + AND f.attrelid = a.adrelid AND f.attname = '$field_name' + AND tab.relname = '$table_name' AND f.attnum = a.adnum"); + $row = @pg_fetch_row($result, 0); + $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]); + $flags .= 'default_' . rawurlencode($num) . ' '; + } + } else { + $flags = ''; + } + $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey + FROM pg_attribute f, pg_class tab, pg_type typ, pg_index i + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attrelid = i.indrelid + AND f.attname = '$field_name' + AND tab.relname = '$table_name'"); + $count = @pg_numrows($result); + + for ($i = 0; $i < $count ; $i++) { + $row = @pg_fetch_row($result, $i); + $keys = explode(' ', $row[2]); + + if (in_array($num_field + 1, $keys)) { + $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : ''; + $flags .= ($row[1] == 't') ? 'primary_key ' : ''; + if (count($keys) > 1) + $flags .= 'multiple_key '; + } + } + + return trim($flags); + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT c.relname AS "Name"' + . ' FROM pg_class c, pg_user u' + . ' WHERE c.relowner = u.usesysid' + . " AND c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . " AND c.relname !~ '^(pg_|sql_)'" + . ' UNION' + . ' SELECT c.relname AS "Name"' + . ' FROM pg_class c' + . " WHERE c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_user' + . ' WHERE usesysid = c.relowner)' + . " AND c.relname !~ '^pg_'"; + case 'schema.tables': + return "SELECT schemaname || '.' || tablename" + . ' AS "Name"' + . ' FROM pg_catalog.pg_tables' + . ' WHERE schemaname NOT IN' + . " ('pg_catalog', 'information_schema', 'pg_toast')"; + case 'views': + // Table cols: viewname | viewowner | definition + return 'SELECT viewname from pg_views WHERE schemaname' + . " NOT IN ('information_schema', 'pg_catalog')"; + case 'users': + // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil + return 'SELECT usename FROM pg_user'; + case 'databases': + return 'SELECT datname FROM pg_database'; + case 'functions': + case 'procedures': + return 'SELECT proname FROM pg_proc WHERE proowner <> 1'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/include/PEAR/DB/storage.php b/include/PEAR/DB/storage.php new file mode 100644 index 0000000..f597b36 --- /dev/null +++ b/include/PEAR/DB/storage.php @@ -0,0 +1,504 @@ + + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: storage.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB class so it can be extended from + */ +require_once 'DB.php'; + +/** + * Provides an object interface to a table row + * + * It lets you add, delete and change rows using objects rather than SQL + * statements. + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_storage extends PEAR +{ + // {{{ properties + + /** the name of the table (or view, if the backend database supports + updates in views) we hold data from */ + var $_table = null; + + /** which column(s) in the table contains primary keys, can be a + string for single-column primary keys, or an array of strings + for multiple-column primary keys */ + var $_keycolumn = null; + + /** DB connection handle used for all transactions */ + var $_dbh = null; + + /** an assoc with the names of database fields stored as properties + in this object */ + var $_properties = array(); + + /** an assoc with the names of the properties in this object that + have been changed since they were fetched from the database */ + var $_changes = array(); + + /** flag that decides if data in this object can be changed. + objects that don't have their table's key column in their + property lists will be flagged as read-only. */ + var $_readonly = false; + + /** function or method that implements a validator for fields that + are set, this validator function returns true if the field is + valid, false if not */ + var $_validator = null; + + // }}} + // {{{ constructor + + /** + * Constructor + * + * @param $table string the name of the database table + * + * @param $keycolumn mixed string with name of key column, or array of + * strings if the table has a primary key of more than one column + * + * @param $dbh object database connection object + * + * @param $validator mixed function or method used to validate + * each new value, called with three parameters: the name of the + * field/column that is changing, a reference to the new value and + * a reference to this object + * + */ + function DB_storage($table, $keycolumn, &$dbh, $validator = null) + { + $this->PEAR('DB_Error'); + $this->_table = $table; + $this->_keycolumn = $keycolumn; + $this->_dbh = $dbh; + $this->_readonly = false; + $this->_validator = $validator; + } + + // }}} + // {{{ _makeWhere() + + /** + * Utility method to build a "WHERE" clause to locate ourselves in + * the table. + * + * XXX future improvement: use rowids? + * + * @access private + */ + function _makeWhere($keyval = null) + { + if (is_array($this->_keycolumn)) { + if ($keyval === null) { + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + $keyval[] = $this->{$this->_keycolumn[$i]}; + } + } + $whereclause = ''; + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + if ($i > 0) { + $whereclause .= ' AND '; + } + $whereclause .= $this->_keycolumn[$i]; + if (is_null($keyval[$i])) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]); + } + } + } else { + if ($keyval === null) { + $keyval = @$this->{$this->_keycolumn}; + } + $whereclause = $this->_keycolumn; + if (is_null($keyval)) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval); + } + } + return $whereclause; + } + + // }}} + // {{{ setup() + + /** + * Method used to initialize a DB_storage object from the + * configured table. + * + * @param $keyval mixed the key[s] of the row to fetch (string or array) + * + * @return int DB_OK on success, a DB error if not + */ + function setup($keyval) + { + $whereclause = $this->_makeWhere($keyval); + $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause; + $sth = $this->_dbh->query($query); + if (DB::isError($sth)) { + return $sth; + } + $row = $sth->fetchRow(DB_FETCHMODE_ASSOC); + if (DB::isError($row)) { + return $row; + } + if (!$row) { + return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null, + $query, null, true); + } + foreach ($row as $key => $value) { + $this->_properties[$key] = true; + $this->$key = $value; + } + return DB_OK; + } + + // }}} + // {{{ insert() + + /** + * Create a new (empty) row in the configured table for this + * object. + */ + function insert($newpk) + { + if (is_array($this->_keycolumn)) { + $primarykey = $this->_keycolumn; + } else { + $primarykey = array($this->_keycolumn); + } + settype($newpk, "array"); + for ($i = 0; $i < sizeof($primarykey); $i++) { + $pkvals[] = $this->_dbh->quote($newpk[$i]); + } + + $sth = $this->_dbh->query("INSERT INTO $this->_table (" . + implode(",", $primarykey) . ") VALUES(" . + implode(",", $pkvals) . ")"); + if (DB::isError($sth)) { + return $sth; + } + if (sizeof($newpk) == 1) { + $newpk = $newpk[0]; + } + $this->setup($newpk); + } + + // }}} + // {{{ toString() + + /** + * Output a simple description of this DB_storage object. + * @return string object description + */ + function toString() + { + $info = strtolower(get_class($this)); + $info .= " (table="; + $info .= $this->_table; + $info .= ", keycolumn="; + if (is_array($this->_keycolumn)) { + $info .= "(" . implode(",", $this->_keycolumn) . ")"; + } else { + $info .= $this->_keycolumn; + } + $info .= ", dbh="; + if (is_object($this->_dbh)) { + $info .= $this->_dbh->toString(); + } else { + $info .= "null"; + } + $info .= ")"; + if (sizeof($this->_properties)) { + $info .= " [loaded, key="; + $keyname = $this->_keycolumn; + if (is_array($keyname)) { + $info .= "("; + for ($i = 0; $i < sizeof($keyname); $i++) { + if ($i > 0) { + $info .= ","; + } + $info .= $this->$keyname[$i]; + } + $info .= ")"; + } else { + $info .= $this->$keyname; + } + $info .= "]"; + } + if (sizeof($this->_changes)) { + $info .= " [modified]"; + } + return $info; + } + + // }}} + // {{{ dump() + + /** + * Dump the contents of this object to "standard output". + */ + function dump() + { + foreach ($this->_properties as $prop => $foo) { + print "$prop = "; + print htmlentities($this->$prop); + print "
\n"; + } + } + + // }}} + // {{{ &create() + + /** + * Static method used to create new DB storage objects. + * @param $data assoc. array where the keys are the names + * of properties/columns + * @return object a new instance of DB_storage or a subclass of it + */ + function &create($table, &$data) + { + $classname = strtolower(get_class($this)); + $obj =& new $classname($table); + foreach ($data as $name => $value) { + $obj->_properties[$name] = true; + $obj->$name = &$value; + } + return $obj; + } + + // }}} + // {{{ loadFromQuery() + + /** + * Loads data into this object from the given query. If this + * object already contains table data, changes will be saved and + * the object re-initialized first. + * + * @param $query SQL query + * + * @param $params parameter list in case you want to use + * prepare/execute mode + * + * @return int DB_OK on success, DB_WARNING_READ_ONLY if the + * returned object is read-only (because the object's specified + * key column was not found among the columns returned by $query), + * or another DB error code in case of errors. + */ +// XXX commented out for now +/* + function loadFromQuery($query, $params = null) + { + if (sizeof($this->_properties)) { + if (sizeof($this->_changes)) { + $this->store(); + $this->_changes = array(); + } + $this->_properties = array(); + } + $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params); + if (DB::isError($rowdata)) { + return $rowdata; + } + reset($rowdata); + $found_keycolumn = false; + while (list($key, $value) = each($rowdata)) { + if ($key == $this->_keycolumn) { + $found_keycolumn = true; + } + $this->_properties[$key] = true; + $this->$key = &$value; + unset($value); // have to unset, or all properties will + // refer to the same value + } + if (!$found_keycolumn) { + $this->_readonly = true; + return DB_WARNING_READ_ONLY; + } + return DB_OK; + } + */ + + // }}} + // {{{ set() + + /** + * Modify an attriute value. + */ + function set($property, $newvalue) + { + // only change if $property is known and object is not + // read-only + if ($this->_readonly) { + return $this->raiseError(null, DB_WARNING_READ_ONLY, null, + null, null, null, true); + } + if (@isset($this->_properties[$property])) { + if (empty($this->_validator)) { + $valid = true; + } else { + $valid = @call_user_func($this->_validator, + $this->_table, + $property, + $newvalue, + $this->$property, + $this); + } + if ($valid) { + $this->$property = $newvalue; + if (empty($this->_changes[$property])) { + $this->_changes[$property] = 0; + } else { + $this->_changes[$property]++; + } + } else { + return $this->raiseError(null, DB_ERROR_INVALID, null, + null, "invalid field: $property", + null, true); + } + return true; + } + return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null, + null, "unknown field: $property", + null, true); + } + + // }}} + // {{{ &get() + + /** + * Fetch an attribute value. + * + * @param string attribute name + * + * @return attribute contents, or null if the attribute name is + * unknown + */ + function &get($property) + { + // only return if $property is known + if (isset($this->_properties[$property])) { + return $this->$property; + } + $tmp = null; + return $tmp; + } + + // }}} + // {{{ _DB_storage() + + /** + * Destructor, calls DB_storage::store() if there are changes + * that are to be kept. + */ + function _DB_storage() + { + if (sizeof($this->_changes)) { + $this->store(); + } + $this->_properties = array(); + $this->_changes = array(); + $this->_table = null; + } + + // }}} + // {{{ store() + + /** + * Stores changes to this object in the database. + * + * @return DB_OK or a DB error + */ + function store() + { + foreach ($this->_changes as $name => $foo) { + $params[] = &$this->$name; + $vars[] = $name . ' = ?'; + } + if ($vars) { + $query = 'UPDATE ' . $this->_table . ' SET ' . + implode(', ', $vars) . ' WHERE ' . + $this->_makeWhere(); + $stmt = $this->_dbh->prepare($query); + $res = $this->_dbh->execute($stmt, $params); + if (DB::isError($res)) { + return $res; + } + $this->_changes = array(); + } + return DB_OK; + } + + // }}} + // {{{ remove() + + /** + * Remove the row represented by this object from the database. + * + * @return mixed DB_OK or a DB error + */ + function remove() + { + if ($this->_readonly) { + return $this->raiseError(null, DB_WARNING_READ_ONLY, null, + null, null, null, true); + } + $query = 'DELETE FROM ' . $this->_table .' WHERE '. + $this->_makeWhere(); + $res = $this->_dbh->query($query); + if (DB::isError($res)) { + return $res; + } + foreach ($this->_properties as $prop => $foo) { + unset($this->$prop); + } + $this->_properties = array(); + $this->_changes = array(); + return DB_OK; + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/include/PEAR/HTTP/Request.php b/include/PEAR/HTTP/Request.php new file mode 100644 index 0000000..8198d00 --- /dev/null +++ b/include/PEAR/HTTP/Request.php @@ -0,0 +1,1143 @@ + | +// +-----------------------------------------------------------------------+ +// +// $Id: Request.php 32 2005-08-01 06:21:02Z dancoulter $ +// +// HTTP_Request Class +// +// Simple example, (Fetches yahoo.com and displays it): +// +// $a = &new HTTP_Request('http://www.yahoo.com/'); +// $a->sendRequest(); +// echo $a->getResponseBody(); +// + +require_once 'PEAR.php'; +require_once 'Net/Socket.php'; +require_once 'Net/URL.php'; + +define('HTTP_REQUEST_METHOD_GET', 'GET', true); +define('HTTP_REQUEST_METHOD_HEAD', 'HEAD', true); +define('HTTP_REQUEST_METHOD_POST', 'POST', true); +define('HTTP_REQUEST_METHOD_PUT', 'PUT', true); +define('HTTP_REQUEST_METHOD_DELETE', 'DELETE', true); +define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true); +define('HTTP_REQUEST_METHOD_TRACE', 'TRACE', true); + +define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true); +define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true); + +class HTTP_Request { + + /** + * Instance of Net_URL + * @var object Net_URL + */ + var $_url; + + /** + * Type of request + * @var string + */ + var $_method; + + /** + * HTTP Version + * @var string + */ + var $_http; + + /** + * Request headers + * @var array + */ + var $_requestHeaders; + + /** + * Basic Auth Username + * @var string + */ + var $_user; + + /** + * Basic Auth Password + * @var string + */ + var $_pass; + + /** + * Socket object + * @var object Net_Socket + */ + var $_sock; + + /** + * Proxy server + * @var string + */ + var $_proxy_host; + + /** + * Proxy port + * @var integer + */ + var $_proxy_port; + + /** + * Proxy username + * @var string + */ + var $_proxy_user; + + /** + * Proxy password + * @var string + */ + var $_proxy_pass; + + /** + * Post data + * @var mixed + */ + var $_postData; + + /** + * Files to post + * @var array + */ + var $_postFiles = array(); + + /** + * Connection timeout. + * @var float + */ + var $_timeout; + + /** + * HTTP_Response object + * @var object HTTP_Response + */ + var $_response; + + /** + * Whether to allow redirects + * @var boolean + */ + var $_allowRedirects; + + /** + * Maximum redirects allowed + * @var integer + */ + var $_maxRedirects; + + /** + * Current number of redirects + * @var integer + */ + var $_redirects; + + /** + * Whether to append brackets [] to array variables + * @var bool + */ + var $_useBrackets = true; + + /** + * Attached listeners + * @var array + */ + var $_listeners = array(); + + /** + * Whether to save response body in response object property + * @var bool + */ + var $_saveBody = true; + + /** + * Timeout for reading from socket (array(seconds, microseconds)) + * @var array + */ + var $_readTimeout = null; + + /** + * Options to pass to Net_Socket::connect. See stream_context_create + * @var array + */ + var $_socketOptions = null; + + /** + * Constructor + * + * Sets up the object + * @param string The url to fetch/access + * @param array Associative array of parameters which can have the following keys: + *
    + *
  • method - Method to use, GET, POST etc (string)
  • + *
  • http - HTTP Version to use, 1.0 or 1.1 (string)
  • + *
  • user - Basic Auth username (string)
  • + *
  • pass - Basic Auth password (string)
  • + *
  • proxy_host - Proxy server host (string)
  • + *
  • proxy_port - Proxy server port (integer)
  • + *
  • proxy_user - Proxy auth username (string)
  • + *
  • proxy_pass - Proxy auth password (string)
  • + *
  • timeout - Connection timeout in seconds (float)
  • + *
  • allowRedirects - Whether to follow redirects or not (bool)
  • + *
  • maxRedirects - Max number of redirects to follow (integer)
  • + *
  • useBrackets - Whether to append [] to array variable names (bool)
  • + *
  • saveBody - Whether to save response body in response object property (bool)
  • + *
  • readTimeout - Timeout for reading / writing data over the socket (array (seconds, microseconds))
  • + *
  • socketOptions - Options to pass to Net_Socket object (array)
  • + *
+ * @access public + */ + function HTTP_Request($url = '', $params = array()) + { + $this->_sock = &new Net_Socket(); + $this->_method = HTTP_REQUEST_METHOD_GET; + $this->_http = HTTP_REQUEST_HTTP_VER_1_1; + $this->_requestHeaders = array(); + $this->_postData = null; + + $this->_user = null; + $this->_pass = null; + + $this->_proxy_host = null; + $this->_proxy_port = null; + $this->_proxy_user = null; + $this->_proxy_pass = null; + + $this->_allowRedirects = false; + $this->_maxRedirects = 3; + $this->_redirects = 0; + + $this->_timeout = null; + $this->_response = null; + + foreach ($params as $key => $value) { + $this->{'_' . $key} = $value; + } + + if (!empty($url)) { + $this->setURL($url); + } + + // Default useragent + $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )'); + + // Make sure keepalives dont knobble us + $this->addHeader('Connection', 'close'); + + // Basic authentication + if (!empty($this->_user)) { + $this->_requestHeaders['Authorization'] = 'Basic ' . base64_encode($this->_user . ':' . $this->_pass); + } + + // Use gzip encoding if possible + // Avoid gzip encoding if using multibyte functions (see #1781) + if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib') && + 0 == (2 & ini_get('mbstring.func_overload'))) { + + $this->addHeader('Accept-Encoding', 'gzip'); + } + } + + /** + * Generates a Host header for HTTP/1.1 requests + * + * @access private + * @return string + */ + function _generateHostHeader() + { + if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) { + $host = $this->_url->host . ':' . $this->_url->port; + + } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) { + $host = $this->_url->host . ':' . $this->_url->port; + + } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) { + $host = $this->_url->host . ':' . $this->_url->port; + + } else { + $host = $this->_url->host; + } + + return $host; + } + + /** + * Resets the object to its initial state (DEPRECATED). + * Takes the same parameters as the constructor. + * + * @param string $url The url to be requested + * @param array $params Associative array of parameters + * (see constructor for details) + * @access public + * @deprecated deprecated since 1.2, call the constructor if this is necessary + */ + function reset($url, $params = array()) + { + $this->HTTP_Request($url, $params); + } + + /** + * Sets the URL to be requested + * + * @param string The url to be requested + * @access public + */ + function setURL($url) + { + $this->_url = &new Net_URL($url, $this->_useBrackets); + + if (!empty($this->_url->user) || !empty($this->_url->pass)) { + $this->setBasicAuth($this->_url->user, $this->_url->pass); + } + + if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) { + $this->addHeader('Host', $this->_generateHostHeader()); + } + } + + /** + * Sets a proxy to be used + * + * @param string Proxy host + * @param int Proxy port + * @param string Proxy username + * @param string Proxy password + * @access public + */ + function setProxy($host, $port = 8080, $user = null, $pass = null) + { + $this->_proxy_host = $host; + $this->_proxy_port = $port; + $this->_proxy_user = $user; + $this->_proxy_pass = $pass; + + if (!empty($user)) { + $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass)); + } + } + + /** + * Sets basic authentication parameters + * + * @param string Username + * @param string Password + */ + function setBasicAuth($user, $pass) + { + $this->_user = $user; + $this->_pass = $pass; + + $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass)); + } + + /** + * Sets the method to be used, GET, POST etc. + * + * @param string Method to use. Use the defined constants for this + * @access public + */ + function setMethod($method) + { + $this->_method = $method; + } + + /** + * Sets the HTTP version to use, 1.0 or 1.1 + * + * @param string Version to use. Use the defined constants for this + * @access public + */ + function setHttpVer($http) + { + $this->_http = $http; + } + + /** + * Adds a request header + * + * @param string Header name + * @param string Header value + * @access public + */ + function addHeader($name, $value) + { + $this->_requestHeaders[$name] = $value; + } + + /** + * Removes a request header + * + * @param string Header name to remove + * @access public + */ + function removeHeader($name) + { + if (isset($this->_requestHeaders[$name])) { + unset($this->_requestHeaders[$name]); + } + } + + /** + * Adds a querystring parameter + * + * @param string Querystring parameter name + * @param string Querystring parameter value + * @param bool Whether the value is already urlencoded or not, default = not + * @access public + */ + function addQueryString($name, $value, $preencoded = false) + { + $this->_url->addQueryString($name, $value, $preencoded); + } + + /** + * Sets the querystring to literally what you supply + * + * @param string The querystring data. Should be of the format foo=bar&x=y etc + * @param bool Whether data is already urlencoded or not, default = already encoded + * @access public + */ + function addRawQueryString($querystring, $preencoded = true) + { + $this->_url->addRawQueryString($querystring, $preencoded); + } + + /** + * Adds postdata items + * + * @param string Post data name + * @param string Post data value + * @param bool Whether data is already urlencoded or not, default = not + * @access public + */ + function addPostData($name, $value, $preencoded = false) + { + if ($preencoded) { + $this->_postData[$name] = $value; + } else { + $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value); + } + } + + /** + * Recursively applies the callback function to the value + * + * @param mixed Callback function + * @param mixed Value to process + * @access private + * @return mixed Processed value + */ + function _arrayMapRecursive($callback, $value) + { + if (!is_array($value)) { + return call_user_func($callback, $value); + } else { + $map = array(); + foreach ($value as $k => $v) { + $map[$k] = $this->_arrayMapRecursive($callback, $v); + } + return $map; + } + } + + /** + * Adds a file to upload + * + * This also changes content-type to 'multipart/form-data' for proper upload + * + * @access public + * @param string name of file-upload field + * @param mixed file name(s) + * @param mixed content-type(s) of file(s) being uploaded + * @return bool true on success + * @throws PEAR_Error + */ + function addFile($inputName, $fileName, $contentType = 'application/octet-stream') + { + if (!is_array($fileName) && !is_readable($fileName)) { + return PEAR::raiseError("File '{$fileName}' is not readable"); + } elseif (is_array($fileName)) { + foreach ($fileName as $name) { + if (!is_readable($name)) { + return PEAR::raiseError("File '{$name}' is not readable"); + } + } + } + $this->addHeader('Content-Type', 'multipart/form-data'); + $this->_postFiles[$inputName] = array( + 'name' => $fileName, + 'type' => $contentType + ); + return true; + } + + /** + * Adds raw postdata + * + * @param string The data + * @param bool Whether data is preencoded or not, default = already encoded + * @access public + */ + function addRawPostData($postdata, $preencoded = true) + { + $this->_postData = $preencoded ? $postdata : urlencode($postdata); + } + + /** + * Clears any postdata that has been added (DEPRECATED). + * + * Useful for multiple request scenarios. + * + * @access public + * @deprecated deprecated since 1.2 + */ + function clearPostData() + { + $this->_postData = null; + } + + /** + * Appends a cookie to "Cookie:" header + * + * @param string $name cookie name + * @param string $value cookie value + * @access public + */ + function addCookie($name, $value) + { + $cookies = isset($this->_requestHeaders['Cookie']) ? $this->_requestHeaders['Cookie']. '; ' : ''; + $this->addHeader('Cookie', $cookies . $name . '=' . $value); + } + + /** + * Clears any cookies that have been added (DEPRECATED). + * + * Useful for multiple request scenarios + * + * @access public + * @deprecated deprecated since 1.2 + */ + function clearCookies() + { + $this->removeHeader('Cookie'); + } + + /** + * Sends the request + * + * @access public + * @param bool Whether to store response body in Response object property, + * set this to false if downloading a LARGE file and using a Listener + * @return mixed PEAR error on error, true otherwise + */ + function sendRequest($saveBody = true) + { + if (!is_a($this->_url, 'Net_URL')) { + return PEAR::raiseError('No URL given.'); + } + + $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host; + $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port; + + // 4.3.0 supports SSL connections using OpenSSL. The function test determines + // we running on at least 4.3.0 + if (strcasecmp($this->_url->protocol, 'https') == 0 AND function_exists('file_get_contents') AND extension_loaded('openssl')) { + if (isset($this->_proxy_host)) { + return PEAR::raiseError('HTTPS proxies are not supported.'); + } + $host = 'ssl://' . $host; + } + + // If this is a second request, we may get away without + // re-connecting if they're on the same server + if (PEAR::isError($err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions)) || + PEAR::isError($err = $this->_sock->write($this->_buildRequest()))) { + + return $err; + } + if (!empty($this->_readTimeout)) { + $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]); + } + + $this->_notify('sentRequest'); + + // Read the response + $this->_response = &new HTTP_Response($this->_sock, $this->_listeners); + if (PEAR::isError($err = $this->_response->process($this->_saveBody && $saveBody)) ) { + return $err; + } + + // Check for redirection + // Bugfix (PEAR) bug #18, 6 oct 2003 by Dave Mertens (headers are also stored lowercase, so we're gonna use them here) + // some non RFC2616 compliant servers (scripts) are returning lowercase headers ('location: xxx') + if ( $this->_allowRedirects + AND $this->_redirects <= $this->_maxRedirects + AND $this->getResponseCode() > 300 + AND $this->getResponseCode() < 399 + AND !empty($this->_response->_headers['location'])) { + + + $redirect = $this->_response->_headers['location']; + + // Absolute URL + if (preg_match('/^https?:\/\//i', $redirect)) { + $this->_url = &new Net_URL($redirect); + $this->addHeader('Host', $this->_generateHostHeader()); + // Absolute path + } elseif ($redirect{0} == '/') { + $this->_url->path = $redirect; + + // Relative path + } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') { + if (substr($this->_url->path, -1) == '/') { + $redirect = $this->_url->path . $redirect; + } else { + $redirect = dirname($this->_url->path) . '/' . $redirect; + } + $redirect = Net_URL::resolvePath($redirect); + $this->_url->path = $redirect; + + // Filename, no path + } else { + if (substr($this->_url->path, -1) == '/') { + $redirect = $this->_url->path . $redirect; + } else { + $redirect = dirname($this->_url->path) . '/' . $redirect; + } + $this->_url->path = $redirect; + } + + $this->_redirects++; + return $this->sendRequest($saveBody); + + // Too many redirects + } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) { + return PEAR::raiseError('Too many redirects'); + } + + $this->_sock->disconnect(); + + return true; + } + + /** + * Returns the response code + * + * @access public + * @return mixed Response code, false if not set + */ + function getResponseCode() + { + return isset($this->_response->_code) ? $this->_response->_code : false; + } + + /** + * Returns either the named header or all if no name given + * + * @access public + * @param string The header name to return, do not set to get all headers + * @return mixed either the value of $headername (false if header is not present) + * or an array of all headers + */ + function getResponseHeader($headername = null) + { + if (!isset($headername)) { + return isset($this->_response->_headers)? $this->_response->_headers: array(); + } else { + return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false; + } + } + + /** + * Returns the body of the response + * + * @access public + * @return mixed response body, false if not set + */ + function getResponseBody() + { + return isset($this->_response->_body) ? $this->_response->_body : false; + } + + /** + * Returns cookies set in response + * + * @access public + * @return mixed array of response cookies, false if none are present + */ + function getResponseCookies() + { + return isset($this->_response->_cookies) ? $this->_response->_cookies : false; + } + + /** + * Builds the request string + * + * @access private + * @return string The request string + */ + function _buildRequest() + { + $separator = ini_get('arg_separator.output'); + ini_set('arg_separator.output', '&'); + $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : ''; + ini_set('arg_separator.output', $separator); + + $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : ''; + $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : ''; + $path = (empty($this->_url->path)? '/': $this->_url->path) . $querystring; + $url = $host . $port . $path; + + $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n"; + + if (HTTP_REQUEST_METHOD_POST != $this->_method && HTTP_REQUEST_METHOD_PUT != $this->_method) { + $this->removeHeader('Content-Type'); + } else { + if (empty($this->_requestHeaders['Content-Type'])) { + // Add default content-type + $this->addHeader('Content-Type', 'application/x-www-form-urlencoded'); + } elseif ('multipart/form-data' == $this->_requestHeaders['Content-Type']) { + $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime()); + $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary); + } + } + + // Request Headers + if (!empty($this->_requestHeaders)) { + foreach ($this->_requestHeaders as $name => $value) { + $request .= $name . ': ' . $value . "\r\n"; + } + } + + // No post data or wrong method, so simply add a final CRLF + if ((HTTP_REQUEST_METHOD_POST != $this->_method && HTTP_REQUEST_METHOD_PUT != $this->_method) || + (empty($this->_postData) && empty($this->_postFiles))) { + + $request .= "\r\n"; + // Post data if it's an array + } elseif ((!empty($this->_postData) && is_array($this->_postData)) || !empty($this->_postFiles)) { + // "normal" POST request + if (!isset($boundary)) { + $postdata = implode('&', array_map( + create_function('$a', 'return $a[0] . \'=\' . $a[1];'), + $this->_flattenArray('', $this->_postData) + )); + + // multipart request, probably with file uploads + } else { + $postdata = ''; + if (!empty($this->_postData)) { + $flatData = $this->_flattenArray('', $this->_postData); + foreach ($flatData as $item) { + $postdata .= '--' . $boundary . "\r\n"; + $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"'; + $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n"; + } + } + foreach ($this->_postFiles as $name => $value) { + if (is_array($value['name'])) { + $varname = $name . ($this->_useBrackets? '[]': ''); + } else { + $varname = $name; + $value['name'] = array($value['name']); + } + foreach ($value['name'] as $key => $filename) { + $fp = fopen($filename, 'r'); + $data = fread($fp, filesize($filename)); + fclose($fp); + $basename = basename($filename); + $type = is_array($value['type'])? @$value['type'][$key]: $value['type']; + + $postdata .= '--' . $boundary . "\r\n"; + $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"'; + $postdata .= "\r\nContent-Type: " . $type; + $postdata .= "\r\n\r\n" . $data . "\r\n"; + } + } + $postdata .= '--' . $boundary . "\r\n"; + } + $request .= 'Content-Length: ' . strlen($postdata) . "\r\n\r\n"; + $request .= $postdata; + + // Post data if it's raw + } elseif(!empty($this->_postData)) { + $request .= 'Content-Length: ' . strlen($this->_postData) . "\r\n\r\n"; + $request .= $this->_postData; + } + + return $request; + } + + /** + * Helper function to change the (probably multidimensional) associative array + * into the simple one. + * + * @param string name for item + * @param mixed item's values + * @return array array with the following items: array('item name', 'item value'); + */ + function _flattenArray($name, $values) + { + if (!is_array($values)) { + return array(array($name, $values)); + } else { + $ret = array(); + foreach ($values as $k => $v) { + if (empty($name)) { + $newName = $k; + } elseif ($this->_useBrackets) { + $newName = $name . '[' . $k . ']'; + } else { + $newName = $name; + } + $ret = array_merge($ret, $this->_flattenArray($newName, $v)); + } + return $ret; + } + } + + + /** + * Adds a Listener to the list of listeners that are notified of + * the object's events + * + * @param object HTTP_Request_Listener instance to attach + * @return boolean whether the listener was successfully attached + * @access public + */ + function attach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener')) { + return false; + } + $this->_listeners[$listener->getId()] =& $listener; + return true; + } + + + /** + * Removes a Listener from the list of listeners + * + * @param object HTTP_Request_Listener instance to detach + * @return boolean whether the listener was successfully detached + * @access public + */ + function detach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener') || + !isset($this->_listeners[$listener->getId()])) { + return false; + } + unset($this->_listeners[$listener->getId()]); + return true; + } + + + /** + * Notifies all registered listeners of an event. + * + * Events sent by HTTP_Request object + * 'sentRequest': after the request was sent + * Events sent by HTTP_Response object + * 'gotHeaders': after receiving response headers (headers are passed in $data) + * 'tick': on receiving a part of response body (the part is passed in $data) + * 'gzTick': on receiving a gzip-encoded part of response body (ditto) + * 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped) + * + * @param string Event name + * @param mixed Additional data + * @access private + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } +} + + +/** +* Response class to complement the Request class +*/ +class HTTP_Response +{ + /** + * Socket object + * @var object + */ + var $_sock; + + /** + * Protocol + * @var string + */ + var $_protocol; + + /** + * Return code + * @var string + */ + var $_code; + + /** + * Response headers + * @var array + */ + var $_headers; + + /** + * Cookies set in response + * @var array + */ + var $_cookies; + + /** + * Response body + * @var string + */ + var $_body = ''; + + /** + * Used by _readChunked(): remaining length of the current chunk + * @var string + */ + var $_chunkLength = 0; + + /** + * Attached listeners + * @var array + */ + var $_listeners = array(); + + /** + * Constructor + * + * @param object Net_Socket socket to read the response from + * @param array listeners attached to request + * @return mixed PEAR Error on error, true otherwise + */ + function HTTP_Response(&$sock, &$listeners) + { + $this->_sock =& $sock; + $this->_listeners =& $listeners; + } + + + /** + * Processes a HTTP response + * + * This extracts response code, headers, cookies and decodes body if it + * was encoded in some way + * + * @access public + * @param bool Whether to store response body in object property, set + * this to false if downloading a LARGE file and using a Listener. + * This is assumed to be true if body is gzip-encoded. + * @throws PEAR_Error + * @return mixed true on success, PEAR_Error in case of malformed response + */ + function process($saveBody = true) + { + do { + $line = $this->_sock->readLine(); + if (sscanf($line, 'HTTP/%s %s', $http_version, $returncode) != 2) { + return PEAR::raiseError('Malformed response.'); + } else { + $this->_protocol = 'HTTP/' . $http_version; + $this->_code = intval($returncode); + } + while ('' !== ($header = $this->_sock->readLine())) { + $this->_processHeader($header); + } + } while (100 == $this->_code); + + $this->_notify('gotHeaders', $this->_headers); + + // If response body is present, read it and decode + $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']); + $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']); + $hasBody = false; + while (!$this->_sock->eof()) { + if ($chunked) { + $data = $this->_readChunked(); + } else { + $data = $this->_sock->read(4096); + } + if ('' != $data) { + $hasBody = true; + if ($saveBody || $gzipped) { + $this->_body .= $data; + } + $this->_notify($gzipped? 'gzTick': 'tick', $data); + } + } + if ($hasBody) { + // Uncompress the body if needed + if ($gzipped) { + $this->_body = gzinflate(substr($this->_body, 10)); + $this->_notify('gotBody', $this->_body); + } else { + $this->_notify('gotBody'); + } + } + return true; + } + + + /** + * Processes the response header + * + * @access private + * @param string HTTP header + */ + function _processHeader($header) + { + list($headername, $headervalue) = explode(':', $header, 2); + $headername_i = strtolower($headername); + $headervalue = ltrim($headervalue); + + if ('set-cookie' != $headername_i) { + $this->_headers[$headername] = $headervalue; + $this->_headers[$headername_i] = $headervalue; + } else { + $this->_parseCookie($headervalue); + } + } + + + /** + * Parse a Set-Cookie header to fill $_cookies array + * + * @access private + * @param string value of Set-Cookie header + */ + function _parseCookie($headervalue) + { + $cookie = array( + 'expires' => null, + 'domain' => null, + 'path' => null, + 'secure' => false + ); + + // Only a name=value pair + if (!strpos($headervalue, ';')) { + $pos = strpos($headervalue, '='); + $cookie['name'] = trim(substr($headervalue, 0, $pos)); + $cookie['value'] = trim(substr($headervalue, $pos + 1)); + + // Some optional parameters are supplied + } else { + $elements = explode(';', $headervalue); + $pos = strpos($elements[0], '='); + $cookie['name'] = trim(substr($elements[0], 0, $pos)); + $cookie['value'] = trim(substr($elements[0], $pos + 1)); + + for ($i = 1; $i < count($elements); $i++) { + if (false === strpos($elements[$i], '=')) { + $elName = trim($elements[$i]); + $elValue = null; + } else { + list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i])); + } + $elName = strtolower($elName); + if ('secure' == $elName) { + $cookie['secure'] = true; + } elseif ('expires' == $elName) { + $cookie['expires'] = str_replace('"', '', $elValue); + } elseif ('path' == $elName || 'domain' == $elName) { + $cookie[$elName] = urldecode($elValue); + } else { + $cookie[$elName] = $elValue; + } + } + } + $this->_cookies[] = $cookie; + } + + + /** + * Read a part of response body encoded with chunked Transfer-Encoding + * + * @access private + * @return string + */ + function _readChunked() + { + // at start of the next chunk? + if (0 == $this->_chunkLength) { + $line = $this->_sock->readLine(); + if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) { + $this->_chunkLength = hexdec($matches[1]); + // Chunk with zero length indicates the end + if (0 == $this->_chunkLength) { + $this->_sock->readAll(); // make this an eof() + return ''; + } + } elseif ($this->_sock->eof()) { + return ''; + } + } + $data = $this->_sock->read($this->_chunkLength); + $this->_chunkLength -= strlen($data); + if (0 == $this->_chunkLength) { + $this->_sock->readLine(); // Trailing CRLF + } + return $data; + } + + + /** + * Notifies all registered listeners of an event. + * + * @param string Event name + * @param mixed Additional data + * @access private + * @see HTTP_Request::_notify() + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } +} // End class HTTP_Response +?> diff --git a/include/PEAR/HTTP/Request/Listener.php b/include/PEAR/HTTP/Request/Listener.php new file mode 100644 index 0000000..883a2e2 --- /dev/null +++ b/include/PEAR/HTTP/Request/Listener.php @@ -0,0 +1,96 @@ + | +// +-----------------------------------------------------------------------+ +// +// $Id: Listener.php 32 2005-08-01 06:21:02Z dancoulter $ +// + +/** + * This class implements the Observer part of a Subject-Observer + * design pattern. It listens to the events sent by a + * HTTP_Request or HTTP_Response instance. + * + * @package HTTP_Request + * @author Alexey Borzov + * @version $Revision: 32 $ + */ +class HTTP_Request_Listener +{ + /** + * A listener's identifier + * @var string + */ + var $_id; + + /** + * Constructor, sets the object's identifier + * + * @access public + */ + function HTTP_Request_Listener() + { + $this->_id = md5(uniqid('http_request_', 1)); + } + + + /** + * Returns the listener's identifier + * + * @access public + * @return string + */ + function getId() + { + return $this->_id; + } + + + /** + * This method is called when Listener is notified of an event + * + * @access public + * @param object an object the listener is attached to + * @param string Event name + * @param mixed Additional data + * @abstract + */ + function update(&$subject, $event, $data = null) + { + echo "Notified of event: '$event'\n"; + if (null !== $data) { + echo "Additional data: "; + var_dump($data); + } + } +} +?> diff --git a/include/PEAR/Log.php b/include/PEAR/Log.php new file mode 100644 index 0000000..322e7b4 --- /dev/null +++ b/include/PEAR/Log.php @@ -0,0 +1,850 @@ + + * @author Jon Parise + * @since Horde 1.3 + * @package Log + */ +class Log +{ + /** + * Indicates whether or not the log can been opened / connected. + * + * @var boolean + * @access protected + */ + var $_opened = false; + + /** + * Instance-specific unique identification number. + * + * @var integer + * @access protected + */ + var $_id = 0; + + /** + * The label that uniquely identifies this set of log messages. + * + * @var string + * @access protected + */ + var $_ident = ''; + + /** + * The default priority to use when logging an event. + * + * @var integer + * @access protected + */ + var $_priority = PEAR_LOG_INFO; + + /** + * The bitmask of allowed log levels. + * + * @var integer + * @access protected + */ + var $_mask = PEAR_LOG_ALL; + + /** + * Holds all Log_observer objects that wish to be notified of new messages. + * + * @var array + * @access protected + */ + var $_listeners = array(); + + /** + * Maps canonical format keys to position arguments for use in building + * "line format" strings. + * + * @var array + * @access protected + */ + var $_formatMap = array('%{timestamp}' => '%1$s', + '%{ident}' => '%2$s', + '%{priority}' => '%3$s', + '%{message}' => '%4$s', + '%{file}' => '%5$s', + '%{line}' => '%6$s', + '%{function}' => '%7$s', + '%\{' => '%%{'); + + /** + * Utility function which wraps PHP's class_exists() function to ensure + * consistent behavior between PHP versions 4 and 5. Autoloading behavior + * is always disabled. + * + * @param string $class The name of the class whose existence should + * be tested. + * + * @return bool True if the class exists. + * + * @access private + * @since Log 1.9.13 + */ + function _classExists($class) + { + if (version_compare(PHP_VERSION, '5.0.0', 'ge')) { + return class_exists($class, false); + } + + return class_exists($class); + } + + /** + * Attempts to return a concrete Log instance of type $handler. + * + * @param string $handler The type of concrete Log subclass to return. + * Attempt to dynamically include the code for + * this subclass. Currently, valid values are + * 'console', 'syslog', 'sql', 'file', and 'mcal'. + * + * @param string $name The name of the actually log file, table, or + * other specific store to use. Defaults to an + * empty string, with which the subclass will + * attempt to do something intelligent. + * + * @param string $ident The identity reported to the log system. + * + * @param array $conf A hash containing any additional configuration + * information that a subclass might need. + * + * @param int $level Log messages up to and including this level. + * + * @return object Log The newly created concrete Log instance, or + * null on an error. + * @access public + * @since Log 1.0 + */ + function &factory($handler, $name = '', $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $handler = strtolower($handler); + $class = 'Log_' . $handler; + $classfile = 'Log/' . $handler . '.php'; + + /* + * Attempt to include our version of the named class, but don't treat + * a failure as fatal. The caller may have already included their own + * version of the named class. + */ + if (!Log::_classExists($class)) { + include_once $classfile; + } + + /* If the class exists, return a new instance of it. */ + if (Log::_classExists($class)) { + $obj = &new $class($name, $ident, $conf, $level); + return $obj; + } + + $null = null; + return $null; + } + + /** + * Attempts to return a reference to a concrete Log instance of type + * $handler, only creating a new instance if no log instance with the same + * parameters currently exists. + * + * You should use this if there are multiple places you might create a + * logger, you don't want to create multiple loggers, and you don't want to + * check for the existance of one each time. The singleton pattern does all + * the checking work for you. + * + * You MUST call this method with the $var = &Log::singleton() syntax. + * Without the ampersand (&) in front of the method name, you will not get + * a reference, you will get a copy. + * + * @param string $handler The type of concrete Log subclass to return. + * Attempt to dynamically include the code for + * this subclass. Currently, valid values are + * 'console', 'syslog', 'sql', 'file', and 'mcal'. + * + * @param string $name The name of the actually log file, table, or + * other specific store to use. Defaults to an + * empty string, with which the subclass will + * attempt to do something intelligent. + * + * @param string $ident The identity reported to the log system. + * + * @param array $conf A hash containing any additional configuration + * information that a subclass might need. + * + * @param int $level Log messages up to and including this level. + * + * @return object Log The newly created concrete Log instance, or + * null on an error. + * @access public + * @since Log 1.0 + */ + function &singleton($handler, $name = '', $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + static $instances; + if (!isset($instances)) $instances = array(); + + $signature = serialize(array($handler, $name, $ident, $conf, $level)); + if (!isset($instances[$signature])) { + $instances[$signature] = &Log::factory($handler, $name, $ident, + $conf, $level); + } + + return $instances[$signature]; + } + + /** + * Abstract implementation of the open() method. + * @since Log 1.0 + */ + function open() + { + return false; + } + + /** + * Abstract implementation of the close() method. + * @since Log 1.0 + */ + function close() + { + return false; + } + + /** + * Abstract implementation of the flush() method. + * @since Log 1.8.2 + */ + function flush() + { + return false; + } + + /** + * Abstract implementation of the log() method. + * @since Log 1.0 + */ + function log($message, $priority = null) + { + return false; + } + + /** + * A convenience function for logging a emergency event. It will log a + * message at the PEAR_LOG_EMERG log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function emerg($message) + { + return $this->log($message, PEAR_LOG_EMERG); + } + + /** + * A convenience function for logging an alert event. It will log a + * message at the PEAR_LOG_ALERT log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function alert($message) + { + return $this->log($message, PEAR_LOG_ALERT); + } + + /** + * A convenience function for logging a critical event. It will log a + * message at the PEAR_LOG_CRIT log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function crit($message) + { + return $this->log($message, PEAR_LOG_CRIT); + } + + /** + * A convenience function for logging a error event. It will log a + * message at the PEAR_LOG_ERR log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function err($message) + { + return $this->log($message, PEAR_LOG_ERR); + } + + /** + * A convenience function for logging a warning event. It will log a + * message at the PEAR_LOG_WARNING log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function warning($message) + { + return $this->log($message, PEAR_LOG_WARNING); + } + + /** + * A convenience function for logging a notice event. It will log a + * message at the PEAR_LOG_NOTICE log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function notice($message) + { + return $this->log($message, PEAR_LOG_NOTICE); + } + + /** + * A convenience function for logging a information event. It will log a + * message at the PEAR_LOG_INFO log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function info($message) + { + return $this->log($message, PEAR_LOG_INFO); + } + + /** + * A convenience function for logging a debug event. It will log a + * message at the PEAR_LOG_DEBUG log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function debug($message) + { + return $this->log($message, PEAR_LOG_DEBUG); + } + + /** + * Returns the string representation of the message data. + * + * If $message is an object, _extractMessage() will attempt to extract + * the message text using a known method (such as a PEAR_Error object's + * getMessage() method). If a known method, cannot be found, the + * serialized representation of the object will be returned. + * + * If the message data is already a string, it will be returned unchanged. + * + * @param mixed $message The original message data. This may be a + * string or any object. + * + * @return string The string representation of the message. + * + * @access protected + */ + function _extractMessage($message) + { + /* + * If we've been given an object, attempt to extract the message using + * a known method. If we can't find such a method, default to the + * "human-readable" version of the object. + * + * We also use the human-readable format for arrays. + */ + if (is_object($message)) { + if (method_exists($message, 'getmessage')) { + $message = $message->getMessage(); + } else if (method_exists($message, 'tostring')) { + $message = $message->toString(); + } else if (method_exists($message, '__tostring')) { + if (version_compare(PHP_VERSION, '5.0.0', 'ge')) { + $message = (string)$message; + } else { + $message = $message->__toString(); + } + } else { + $message = var_export($message, true); + } + } else if (is_array($message)) { + if (isset($message['message'])) { + $message = $message['message']; + } else { + $message = var_export($message, true); + } + } else if (is_bool($message) || $message === NULL) { + $message = var_export($message, true); + } + + /* Otherwise, we assume the message is a string. */ + return $message; + } + + /** + * Using debug_backtrace(), returns the file, line, and enclosing function + * name of the source code context from which log() was invoked. + * + * @param int $depth The initial number of frames we should step + * back into the trace. + * + * @return array Array containing three strings: the filename, the line, + * and the function name from which log() was called. + * + * @access private + * @since Log 1.9.4 + */ + function _getBacktraceVars($depth) + { + /* Start by generating a backtrace from the current call (here). */ + $bt = debug_backtrace(); + + /* + * If we were ultimately invoked by the composite handler, we need to + * increase our depth one additional level to compensate. + */ + $class = isset($bt[$depth+1]['class']) ? $bt[$depth+1]['class'] : null; + if ($class !== null && strcasecmp($class, 'Log_composite') == 0) { + $depth++; + } + + /* + * We're interested in the frame which invoked the log() function, so + * we need to walk back some number of frames into the backtrace. The + * $depth parameter tells us where to start looking. We go one step + * further back to find the name of the encapsulating function from + * which log() was called. + */ + $file = isset($bt[$depth]) ? $bt[$depth]['file'] : null; + $line = isset($bt[$depth]) ? $bt[$depth]['line'] : 0; + $func = isset($bt[$depth + 1]) ? $bt[$depth + 1]['function'] : null; + + /* + * However, if log() was called from one of our "shortcut" functions, + * we're going to need to go back an additional step. + */ + if (in_array($func, array('emerg', 'alert', 'crit', 'err', 'warning', + 'notice', 'info', 'debug'))) { + $file = isset($bt[$depth + 1]) ? $bt[$depth + 1]['file'] : null; + $line = isset($bt[$depth + 1]) ? $bt[$depth + 1]['line'] : 0; + $func = isset($bt[$depth + 2]) ? $bt[$depth + 2]['function'] : null; + } + + /* + * If we couldn't extract a function name (perhaps because we were + * executed from the "main" context), provide a default value. + */ + if (is_null($func)) { + $func = '(none)'; + } + + /* Return a 3-tuple containing (file, line, function). */ + return array($file, $line, $func); + } + + /** + * Produces a formatted log line based on a format string and a set of + * variables representing the current log record and state. + * + * @return string Formatted log string. + * + * @access protected + * @since Log 1.9.4 + */ + function _format($format, $timestamp, $priority, $message) + { + /* + * If the format string references any of the backtrace-driven + * variables (%5, %6, %7), generate the backtrace and fetch them. + */ + if (strpos($format, '%5') || strpos($format, '%6') || strpos($format, '%7')) { + list($file, $line, $func) = $this->_getBacktraceVars(2); + } + + /* + * Build the formatted string. We use the sprintf() function's + * "argument swapping" capability to dynamically select and position + * the variables which will ultimately appear in the log string. + */ + return sprintf($format, + $timestamp, + $this->_ident, + $this->priorityToString($priority), + $message, + isset($file) ? $file : '', + isset($line) ? $line : '', + isset($func) ? $func : ''); + } + + /** + * Returns the string representation of a PEAR_LOG_* integer constant. + * + * @param int $priority A PEAR_LOG_* integer constant. + * + * @return string The string representation of $level. + * + * @access public + * @since Log 1.0 + */ + function priorityToString($priority) + { + $levels = array( + PEAR_LOG_EMERG => 'emergency', + PEAR_LOG_ALERT => 'alert', + PEAR_LOG_CRIT => 'critical', + PEAR_LOG_ERR => 'error', + PEAR_LOG_WARNING => 'warning', + PEAR_LOG_NOTICE => 'notice', + PEAR_LOG_INFO => 'info', + PEAR_LOG_DEBUG => 'debug' + ); + + return $levels[$priority]; + } + + /** + * Returns the the PEAR_LOG_* integer constant for the given string + * representation of a priority name. This function performs a + * case-insensitive search. + * + * @param string $name String containing a priority name. + * + * @return string The PEAR_LOG_* integer contstant corresponding + * the the specified priority name. + * + * @access public + * @since Log 1.9.0 + */ + function stringToPriority($name) + { + $levels = array( + 'emergency' => PEAR_LOG_EMERG, + 'alert' => PEAR_LOG_ALERT, + 'critical' => PEAR_LOG_CRIT, + 'error' => PEAR_LOG_ERR, + 'warning' => PEAR_LOG_WARNING, + 'notice' => PEAR_LOG_NOTICE, + 'info' => PEAR_LOG_INFO, + 'debug' => PEAR_LOG_DEBUG + ); + + return $levels[strtolower($name)]; + } + + /** + * Calculate the log mask for the given priority. + * + * This method may be called statically. + * + * @param integer $priority The priority whose mask will be calculated. + * + * @return integer The calculated log mask. + * + * @access public + * @since Log 1.7.0 + */ + function MASK($priority) + { + return (1 << $priority); + } + + /** + * Calculate the log mask for all priorities up to the given priority. + * + * This method may be called statically. + * + * @param integer $priority The maximum priority covered by this mask. + * + * @return integer The resulting log mask. + * + * @access public + * @since Log 1.7.0 + * + * @deprecated deprecated since Log 1.9.4; use Log::MAX() instead + */ + function UPTO($priority) + { + return Log::MAX($priority); + } + + /** + * Calculate the log mask for all priorities greater than or equal to the + * given priority. In other words, $priority will be the lowest priority + * matched by the resulting mask. + * + * This method may be called statically. + * + * @param integer $priority The minimum priority covered by this mask. + * + * @return integer The resulting log mask. + * + * @access public + * @since Log 1.9.4 + */ + function MIN($priority) + { + return PEAR_LOG_ALL ^ ((1 << $priority) - 1); + } + + /** + * Calculate the log mask for all priorities less than or equal to the + * given priority. In other words, $priority will be the highests priority + * matched by the resulting mask. + * + * This method may be called statically. + * + * @param integer $priority The maximum priority covered by this mask. + * + * @return integer The resulting log mask. + * + * @access public + * @since Log 1.9.4 + */ + function MAX($priority) + { + return ((1 << ($priority + 1)) - 1); + } + + /** + * Set and return the level mask for the current Log instance. + * + * @param integer $mask A bitwise mask of log levels. + * + * @return integer The current level mask. + * + * @access public + * @since Log 1.7.0 + */ + function setMask($mask) + { + $this->_mask = $mask; + + return $this->_mask; + } + + /** + * Returns the current level mask. + * + * @return interger The current level mask. + * + * @access public + * @since Log 1.7.0 + */ + function getMask() + { + return $this->_mask; + } + + /** + * Check if the given priority is included in the current level mask. + * + * @param integer $priority The priority to check. + * + * @return boolean True if the given priority is included in the current + * log mask. + * + * @access protected + * @since Log 1.7.0 + */ + function _isMasked($priority) + { + return (Log::MASK($priority) & $this->_mask); + } + + /** + * Returns the current default priority. + * + * @return integer The current default priority. + * + * @access public + * @since Log 1.8.4 + */ + function getPriority() + { + return $this->_priority; + } + + /** + * Sets the default priority to the specified value. + * + * @param integer $priority The new default priority. + * + * @access public + * @since Log 1.8.4 + */ + function setPriority($priority) + { + $this->_priority = $priority; + } + + /** + * Adds a Log_observer instance to the list of observers that are listening + * for messages emitted by this Log instance. + * + * @param object $observer The Log_observer instance to attach as a + * listener. + * + * @param boolean True if the observer is successfully attached. + * + * @access public + * @since Log 1.0 + */ + function attach(&$observer) + { + if (!is_a($observer, 'Log_observer')) { + return false; + } + + $this->_listeners[$observer->_id] = &$observer; + + return true; + } + + /** + * Removes a Log_observer instance from the list of observers. + * + * @param object $observer The Log_observer instance to detach from + * the list of listeners. + * + * @param boolean True if the observer is successfully detached. + * + * @access public + * @since Log 1.0 + */ + function detach($observer) + { + if (!is_a($observer, 'Log_observer') || + !isset($this->_listeners[$observer->_id])) { + return false; + } + + unset($this->_listeners[$observer->_id]); + + return true; + } + + /** + * Informs each registered observer instance that a new message has been + * logged. + * + * @param array $event A hash describing the log event. + * + * @access protected + */ + function _announce($event) + { + foreach ($this->_listeners as $id => $listener) { + if ($event['priority'] <= $this->_listeners[$id]->_priority) { + $this->_listeners[$id]->notify($event); + } + } + } + + /** + * Indicates whether this is a composite class. + * + * @return boolean True if this is a composite class. + * + * @access public + * @since Log 1.0 + */ + function isComposite() + { + return false; + } + + /** + * Sets this Log instance's identification string. + * + * @param string $ident The new identification string. + * + * @access public + * @since Log 1.6.3 + */ + function setIdent($ident) + { + $this->_ident = $ident; + } + + /** + * Returns the current identification string. + * + * @return string The current Log instance's identification string. + * + * @access public + * @since Log 1.6.3 + */ + function getIdent() + { + return $this->_ident; + } +} diff --git a/include/PEAR/Log/composite.php b/include/PEAR/Log/composite.php new file mode 100644 index 0000000..98a1d81 --- /dev/null +++ b/include/PEAR/Log/composite.php @@ -0,0 +1,231 @@ + + * @author Jon Parise + * + * @since Horde 1.3 + * @since Log 1.0 + * @package Log + * + * @example composite.php Using the composite handler. + */ +class Log_composite extends Log +{ + /** + * Array holding all of the Log instances to which log events should be + * sent. + * + * @var array + * @access private + */ + var $_children = array(); + + + /** + * Constructs a new composite Log object. + * + * @param boolean $name This parameter is ignored. + * @param boolean $ident This parameter is ignored. + * @param boolean $conf This parameter is ignored. + * @param boolean $level This parameter is ignored. + * + * @access public + */ + function Log_composite($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_ident = $ident; + } + + /** + * Opens all of the child instances. + * + * @return True if all of the child instances were successfully opened. + * + * @access public + */ + function open() + { + /* Attempt to open each of our children. */ + $this->_opened = true; + foreach ($this->_children as $id => $child) { + $this->_opened &= $this->_children[$id]->open(); + } + + /* If all children were opened, return success. */ + return $this->_opened; + } + + /** + * Closes all of the child instances. + * + * @return True if all of the child instances were successfully closed. + * + * @access public + */ + function close() + { + /* Attempt to close each of our children. */ + $closed = true; + foreach ($this->_children as $id => $child) { + $closed &= $this->_children[$id]->close(); + } + + /* Track the _opened state for consistency. */ + $this->_opened = false; + + /* If all children were closed, return success. */ + return $closed; + } + + /** + * Flushes all child instances. It is assumed that all of the children + * have been successfully opened. + * + * @return True if all of the child instances were successfully flushed. + * + * @access public + * @since Log 1.8.2 + */ + function flush() + { + /* Attempt to flush each of our children. */ + $flushed = true; + foreach ($this->_children as $id => $child) { + $flushed &= $this->_children[$id]->flush(); + } + + /* If all children were flushed, return success. */ + return $flushed; + } + + /** + * Sends $message and $priority to each child of this composite. If the + * children aren't already open, they will be opened here. + * + * @param mixed $message String or object containing the message + * to log. + * @param string $priority (optional) The priority of the message. + * Valid values are: PEAR_LOG_EMERG, + * PEAR_LOG_ALERT, PEAR_LOG_CRIT, + * PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and + * PEAR_LOG_DEBUG. + * + * @return boolean True if the entry is successfully logged. + * + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* + * If the handlers haven't been opened, attempt to open them now. + * However, we don't treat failure to open all of the handlers as a + * fatal error. We defer that consideration to the success of calling + * each handler's log() method below. + */ + if (!$this->_opened) { + $this->open(); + } + + /* Attempt to log the event using each of the children. */ + $success = true; + foreach ($this->_children as $id => $child) { + $success &= $this->_children[$id]->log($message, $priority); + } + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + /* Return success if all of the children logged the event. */ + return $success; + } + + /** + * Returns true if this is a composite. + * + * @return boolean True if this is a composite class. + * + * @access public + */ + function isComposite() + { + return true; + } + + /** + * Sets this identification string for all of this composite's children. + * + * @param string $ident The new identification string. + * + * @access public + * @since Log 1.6.7 + */ + function setIdent($ident) + { + /* Call our base class's setIdent() method. */ + parent::setIdent($ident); + + /* ... and then call setIdent() on all of our children. */ + foreach ($this->_children as $id => $child) { + $this->_children[$id]->setIdent($ident); + } + } + + /** + * Adds a Log instance to the list of children. + * + * @param object $child The Log instance to add. + * + * @return boolean True if the Log instance was successfully added. + * + * @access public + */ + function addChild(&$child) + { + /* Make sure this is a Log instance. */ + if (!is_a($child, 'Log')) { + return false; + } + + $this->_children[$child->_id] = &$child; + + return true; + } + + /** + * Removes a Log instance from the list of children. + * + * @param object $child The Log instance to remove. + * + * @return boolean True if the Log instance was successfully removed. + * + * @access public + */ + function removeChild($child) + { + if (!is_a($child, 'Log') || !isset($this->_children[$child->_id])) { + return false; + } + + unset($this->_children[$child->_id]); + + return true; + } + +} diff --git a/include/PEAR/Log/console.php b/include/PEAR/Log/console.php new file mode 100644 index 0000000..0c1f9b6 --- /dev/null +++ b/include/PEAR/Log/console.php @@ -0,0 +1,208 @@ + + * @since Log 1.1 + * @package Log + * + * @example console.php Using the console handler. + */ +class Log_console extends Log +{ + /** + * Handle to the current output stream. + * @var resource + * @access private + */ + var $_stream = STDOUT; + + /** + * Should the output be buffered or displayed immediately? + * @var string + * @access private + */ + var $_buffering = false; + + /** + * String holding the buffered output. + * @var string + * @access private + */ + var $_buffer = ''; + + /** + * String containing the format of a log line. + * @var string + * @access private + */ + var $_lineFormat = '%1$s %2$s [%3$s] %4$s'; + + /** + * String containing the timestamp format. It will be passed directly to + * strftime(). Note that the timestamp string will generated using the + * current locale. + * @var string + * @access private + */ + var $_timeFormat = '%b %d %H:%M:%S'; + + /** + * Constructs a new Log_console object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_console($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (!empty($conf['stream'])) { + $this->_stream = $conf['stream']; + } + + if (isset($conf['buffering'])) { + $this->_buffering = $conf['buffering']; + } + + if (!empty($conf['lineFormat'])) { + $this->_lineFormat = str_replace(array_keys($this->_formatMap), + array_values($this->_formatMap), + $conf['lineFormat']); + } + + if (!empty($conf['timeFormat'])) { + $this->_timeFormat = $conf['timeFormat']; + } + + /* + * If output buffering has been requested, we need to register a + * shutdown function that will dump the buffer upon termination. + */ + if ($this->_buffering) { + register_shutdown_function(array(&$this, '_Log_console')); + } + } + + /** + * Destructor + */ + function _Log_console() + { + $this->close(); + } + + /** + * Open the output stream. + * + * @access public + * @since Log 1.9.7 + */ + function open() + { + $this->_opened = true; + return true; + } + + /** + * Closes the output stream. + * + * This results in a call to flush(). + * + * @access public + * @since Log 1.9.0 + */ + function close() + { + $this->flush(); + $this->_opened = false; + return true; + } + + /** + * Flushes all pending ("buffered") data to the output stream. + * + * @access public + * @since Log 1.8.2 + */ + function flush() + { + /* + * If output buffering is enabled, dump the contents of the buffer to + * the output stream. + */ + if ($this->_buffering && (strlen($this->_buffer) > 0)) { + fwrite($this->_stream, $this->_buffer); + $this->_buffer = ''; + } + + if (is_resource($this->_stream)) { + return fflush($this->_stream); + } + + return false; + } + + /** + * Writes $message to the text console. Also, passes the message + * along to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build the string containing the complete log line. */ + $line = $this->_format($this->_lineFormat, + strftime($this->_timeFormat), + $priority, $message) . "\n"; + + /* + * If buffering is enabled, append this line to the output buffer. + * Otherwise, print the line to the output stream immediately. + */ + if ($this->_buffering) { + $this->_buffer .= $line; + } else { + fwrite($this->_stream, $line); + } + + /* Notify observers about this log message. */ + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/include/PEAR/Log/daemon.php b/include/PEAR/Log/daemon.php new file mode 100644 index 0000000..d5e2aee --- /dev/null +++ b/include/PEAR/Log/daemon.php @@ -0,0 +1,235 @@ + + * @version $Revision: 1.3 $ + * @package Log + */ +class Log_daemon extends Log +{ + /** + * Integer holding the log facility to use. + * @var string + */ + var $_name = LOG_DAEMON; + + /** + * Var holding the resource pointer to the socket + * @var resource + */ + var $_socket; + + /** + * The ip address or servername + * @see http://www.php.net/manual/en/transports.php + * @var string + */ + var $_ip = '127.0.0.1'; + + /** + * Protocol to use (tcp, udp, etc.) + * @see http://www.php.net/manual/en/transports.php + * @var string + */ + var $_proto = 'udp'; + + /** + * Port to connect to + * @var int + */ + var $_port = 514; + + /** + * Maximum message length in bytes + * @var int + */ + var $_maxsize = 4096; + + /** + * Socket timeout in seconds + * @var int + */ + var $_timeout = 1; + + + /** + * Constructs a new syslog object. + * + * @param string $name The syslog facility. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $maxLevel Maximum level at which to log. + * @access public + */ + function Log_daemon($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + /* Ensure we have a valid integer value for $name. */ + if (empty($name) || !is_int($name)) { + $name = LOG_SYSLOG; + } + + $this->_id = md5(microtime()); + $this->_name = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (isset($conf['ip'])) { + $this->_ip = $conf['ip']; + } + if (isset($conf['proto'])) { + $this->_proto = $conf['proto']; + } + if (isset($conf['port'])) { + $this->_port = $conf['port']; + } + if (isset($conf['maxsize'])) { + $this->_maxsize = $conf['maxsize']; + } + if (isset($conf['timeout'])) { + $this->_timeout = $conf['timeout']; + } + $this->_proto = $this->_proto . '://'; + + register_shutdown_function(array(&$this, '_Log_daemon')); + } + + /** + * Destructor. + * + * @access private + */ + function _Log_daemon() + { + $this->close(); + } + + /** + * Opens a connection to the system logger, if it has not already + * been opened. This is implicitly called by log(), if necessary. + * @access public + */ + function open() + { + if (!$this->_opened) { + $this->_opened = (bool)($this->_socket = @fsockopen( + $this->_proto . $this->_ip, + $this->_port, + $errno, + $errstr, + $this->_timeout)); + } + return $this->_opened; + } + + /** + * Closes the connection to the system logger, if it is open. + * @access public + */ + function close() + { + if ($this->_opened) { + $this->_opened = false; + return fclose($this->_socket); + } + return true; + } + + /** + * Sends $message to the currently open syslog connection. Calls + * open() if necessary. Also passes the message along to any Log_observer + * instances that are observing this Log. + * + * @param string $message The textual message to be logged. + * @param int $priority (optional) The priority of the message. Valid + * values are: LOG_EMERG, LOG_ALERT, LOG_CRIT, + * LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, + * and LOG_DEBUG. The default is LOG_INFO. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Set the facility level. */ + $facility_level = intval($this->_name) + + intval($this->_toSyslog($priority)); + + /* Prepend ident info. */ + if (!empty($this->_ident)) { + $message = $this->_ident . ' ' . $message; + } + + /* Check for message length. */ + if (strlen($message) > $this->_maxsize) { + $message = substr($message, 0, ($this->_maxsize) - 10) . ' [...]'; + } + + /* Write to socket. */ + fwrite($this->_socket, '<' . $facility_level . '>' . $message . "\n"); + + $this->_announce(array('priority' => $priority, 'message' => $message)); + } + + /** + * Converts a PEAR_LOG_* constant into a syslog LOG_* constant. + * + * This function exists because, under Windows, not all of the LOG_* + * constants have unique values. Instead, the PEAR_LOG_* were introduced + * for global use, with the conversion to the LOG_* constants kept local to + * to the syslog driver. + * + * @param int $priority PEAR_LOG_* value to convert to LOG_* value. + * + * @return The LOG_* representation of $priority. + * + * @access private + */ + function _toSyslog($priority) + { + static $priorities = array( + PEAR_LOG_EMERG => LOG_EMERG, + PEAR_LOG_ALERT => LOG_ALERT, + PEAR_LOG_CRIT => LOG_CRIT, + PEAR_LOG_ERR => LOG_ERR, + PEAR_LOG_WARNING => LOG_WARNING, + PEAR_LOG_NOTICE => LOG_NOTICE, + PEAR_LOG_INFO => LOG_INFO, + PEAR_LOG_DEBUG => LOG_DEBUG + ); + + /* If we're passed an unknown priority, default to LOG_INFO. */ + if (!is_int($priority) || !in_array($priority, $priorities)) { + return LOG_INFO; + } + + return $priorities[$priority]; + } + +} diff --git a/include/PEAR/Log/display.php b/include/PEAR/Log/display.php new file mode 100644 index 0000000..8e09f5c --- /dev/null +++ b/include/PEAR/Log/display.php @@ -0,0 +1,161 @@ + + * @since Log 1.8.0 + * @package Log + * + * @example display.php Using the display handler. + */ +class Log_display extends Log +{ + /** + * String containing the format of a log line. + * @var string + * @access private + */ + var $_lineFormat = '%3$s: %4$s'; + + /** + * String containing the timestamp format. It will be passed directly to + * strftime(). Note that the timestamp string will generated using the + * current locale. + * @var string + * @access private + */ + var $_timeFormat = '%b %d %H:%M:%S'; + + /** + * Constructs a new Log_display object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_display($name = '', $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + /* Start by configuring the line format. */ + if (!empty($conf['lineFormat'])) { + $this->_lineFormat = str_replace(array_keys($this->_formatMap), + array_values($this->_formatMap), + $conf['lineFormat']); + } + + /* We may need to prepend a string to our line format. */ + $prepend = null; + if (isset($conf['error_prepend'])) { + $prepend = $conf['error_prepend']; + } else { + $prepend = ini_get('error_prepend_string'); + } + if (!empty($prepend)) { + $this->_lineFormat = $prepend . $this->_lineFormat; + } + + /* We may also need to append a string to our line format. */ + $append = null; + if (isset($conf['error_append'])) { + $append = $conf['error_append']; + } else { + $append = ini_get('error_append_string'); + } + if (!empty($append)) { + $this->_lineFormat .= $append; + } + + /* Lastly, the line ending sequence is also configurable. */ + if (isset($conf['linebreak'])) { + $this->_lineFormat .= $conf['linebreak']; + } else { + $this->_lineFormat .= "
\n"; + } + + /* The user can also change the time format. */ + if (!empty($conf['timeFormat'])) { + $this->_timeFormat = $conf['timeFormat']; + } + } + + /** + * Opens the display handler. + * + * @access public + * @since Log 1.9.6 + */ + function open() + { + $this->_opened = true; + return true; + } + + /** + * Closes the display handler. + * + * @access public + * @since Log 1.9.6 + */ + function close() + { + $this->_opened = false; + return true; + } + + /** + * Writes $message to the text browser. Also, passes the message + * along to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build and output the complete log line. */ + echo $this->_format($this->_lineFormat, + strftime($this->_timeFormat), + $priority, + nl2br(htmlspecialchars($message))); + + /* Notify observers about this log message. */ + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/include/PEAR/Log/docs/guide.txt b/include/PEAR/Log/docs/guide.txt new file mode 100644 index 0000000..94e6108 --- /dev/null +++ b/include/PEAR/Log/docs/guide.txt @@ -0,0 +1,1246 @@ +================= + The Log Package +================= + +-------------------- + User Documentation +-------------------- + +:Author: Jon Parise +:Contact: jon@php.net +:Date: $Date: 2008/02/10 00:15:04 $ +:Revision: $Revision: 1.39 $ + +.. contents:: Contents +.. section-numbering:: + +Using Log Handlers +================== + +The Log package is implemented as a framework that supports the notion of +backend-specific log handlers. The base logging object (defined by the `Log +class`_) is primarily an abstract interface to the currently configured +handler. + +A wide variety of handlers are distributed with the Log package, and, should +none of them fit your application's needs, it's easy to `write your own`__. + +.. _Log class: http://cvs.php.net/viewvc.cgi/pear/Log/Log.php +__ `Custom Handlers`_ + +Creating a Log Object +--------------------- +There are three ways to create Log objects: + + - Using the ``Log::factory()`` method + - Using the ``Log::singleton()`` method + - Direct instantiation + +The Factory Method +~~~~~~~~~~~~~~~~~~ +The ``Log::factory()`` method implements the `Factory Pattern`_. It allows +for the parameterized construction of concrete Log instances at runtime. The +first parameter to the ``Log::factory()`` method indicates the name of the +concrete handler to create. The rest of the parameters will be passed on to +the handler's constructor (see `Configuring a Handler`_ below). + +The new ``Log`` instance is returned by reference. + +:: + + require_once 'Log.php'; + + $console = &Log::factory('console', '', 'TEST'); + $console->log('Logging to the console.'); + + $file = &Log::factory('file', 'out.log', 'TEST'); + $file->log('Logging to out.log.'); + +.. _Factory Pattern: http://wikipedia.org/wiki/Factory_method_pattern + +The Singleton Method +~~~~~~~~~~~~~~~~~~~~ +The ``Log::singleton()`` method implements the `Singleton Pattern`_. The +singleton pattern ensures that only a single instance of a given log type and +configuration is ever created. This has two benefits: first, it prevents +duplicate ``Log`` instances from being constructed, and, second, it gives all +of your code access to the same ``Log`` instance. The latter is especially +important when logging to files because only a single file handler will need +to be managed. + +The ``Log::singleton()`` method's parameters match the ``Log::factory()`` +method. The new ``Log`` instance is returned by reference. + +:: + + require_once 'Log.php'; + + /* Same construction parameters */ + $a = &Log::singleton('console', '', 'TEST'); + $b = &Log::singleton('console', '', 'TEST'); + + if ($a === $b) { + echo '$a and $b point to the same Log instance.' . "\n"; + } + + /* Different construction parameters */ + $c = &Log::singleton('console', '', 'TEST1'); + $d = &Log::singleton('console', '', 'TEST2'); + + if ($c !== $d) { + echo '$c and $d point to different Log instances.' . "\n"; + } + +.. _Singleton Pattern: http://wikipedia.org/wiki/Singleton_pattern + +Direct Instantiation +~~~~~~~~~~~~~~~~~~~~ +It is also possible to directly instantiate concrete ``Log`` handler +instances. However, this method is **not recommended** because it creates a +tighter coupling between your application code and the Log package than is +necessary. Use of `the factory method`_ or `the singleton method`_ is +preferred. + + +Configuring a Handler +--------------------- +A log handler's configuration is determined by the arguments used in its +construction. Here's an overview of those parameters:: + + /* Using the factory method ... */ + &Log::factory($handler, $name, $ident, $conf, $maxLevel); + + /* Using the singleton method ... */ + &Log::singleton($handler, $name, $ident, $conf, $maxLevel); + + /* Using direct instantiation ... */ + new Log_handler($name, $ident, $conf, $maxLevel); + ++---------------+-----------+-----------------------------------------------+ +| Parameter | Type | Description | ++===============+===========+===============================================+ +| ``$handler`` | String | The type of Log handler to construct. This | +| | | parameter is only available when `the factory | +| | | method`_ or `the singleton method`_ are used. | ++---------------+-----------+-----------------------------------------------+ +| ``$name`` | String | The name of the log resource to which the | +| | | events will be logged. The use of this value | +| | | is determined by the handler's implementation.| +| | | It defaults to an empty string. | ++---------------+-----------+-----------------------------------------------+ +| ``$ident`` | String | An identification string that will be included| +| | | in all log events logged by this handler. | +| | | This value defaults to an empty string and can| +| | | be changed at runtime using the ``setIdent()``| +| | | method. | ++---------------+-----------+-----------------------------------------------+ +| ``$conf`` | Array | Associative array of key-value pairs that are | +| | | used to specify any handler-specific settings.| ++---------------+-----------+-----------------------------------------------+ +| ``$level`` | Integer | Log messages up to and including this level. | +| | | This value defaults to ``PEAR_LOG_DEBUG``. | +| | | See `Log Levels`_ and `Log Level Masks`_. | ++---------------+-----------+-----------------------------------------------+ + + +Logging an Event +---------------- +Events are logged using the ``log()`` method:: + + $logger->log('Message', PEAR_LOG_NOTICE); + +The first argument contains the log event's message. Even though the event is +always logged as a string, it is possible to pass an object to the ``log()`` +method. If the object implements a ``getString()`` method, a ``toString()`` +method or Zend Engine 2's special ``__toString()`` casting method, it will be +used to determine the object's string representation. Otherwise, the +`serialized`_ form of the object will be logged. + +The second, optional argument specifies the log event's priority. See the +`Log Levels`_ table for the complete list of priorities. The default priority +is PEAR_LOG_INFO. + +The ``log()`` method will return ``true`` if the event was successfully +logged. + +"Shortcut" methods are also available for logging an event at a specific log +level. See the `Log Levels`_ table for the complete list. + +.. _serialized: http://www.php.net/serialize + + +Log Levels +---------- +This table is ordered by highest priority (``PEAR_LOG_EMERG``) to lowest +priority (``PEAR_LOG_DEBUG``). + ++-----------------------+---------------+-----------------------------------+ +| Level | Shortcut | Description | ++=======================+===============+===================================+ +| ``PEAR_LOG_EMERG`` | ``emerg()`` | System is unusable | ++-----------------------+---------------+-----------------------------------+ +| ``PEAR_LOG_ALERT`` | ``alert()`` | Immediate action required | ++-----------------------+---------------+-----------------------------------+ +| ``PEAR_LOG_CRIT`` | ``crit()`` | Critical conditions | ++-----------------------+---------------+-----------------------------------+ +| ``PEAR_LOG_ERR`` | ``err()`` | Error conditions | ++-----------------------+---------------+-----------------------------------+ +| ``PEAR_LOG_WARNING`` | ``warning()`` | Warning conditions | ++-----------------------+---------------+-----------------------------------+ +| ``PEAR_LOG_NOTICE`` | ``notice()`` | Normal but significant | ++-----------------------+---------------+-----------------------------------+ +| ``PEAR_LOG_INFO`` | ``info()`` | Informational | ++-----------------------+---------------+-----------------------------------+ +| ``PEAR_LOG_DEBUG`` | ``debug()`` | Debug-level messages | ++-----------------------+---------------+-----------------------------------+ + + +Log Level Masks +--------------- +Defining a log level mask allows you to include and/or exclude specific levels +of events from being logged. The ``$level`` construction parameter (see +`Configuring a Handler`_) uses this mechanism to exclude log events below a +certain priority, and it's possible to define more complex masks once the Log +object has been constructed. + +Each priority has a specific mask associated with it. To compute a priority's +mask, use the static ``Log::MASK()`` method:: + + $mask = Log::MASK(PEAR_LOG_INFO); + +To compute the mask for all priorities up to, and including, a certain level, +use the ``Log::MAX()`` static method:: + + $mask = Log::MAX(PEAR_LOG_INFO); + +To compute the mask for all priorities greater than or equal to a certain +level, use the ``Log::MIN()`` static method:: + + $mask = Log::MIN(PEAR_LOG_INFO); + +The apply the mask, use the ``setMask()`` method:: + + $logger->setMask($mask); + +Masks can be be combined using bitwise operations. To restrict logging to +only those events marked as ``PEAR_LOG_NOTICE`` or ``PEAR_LOG_DEBUG``:: + + $mask = Log::MASK(PEAR_LOG_NOTICE) | Log::MASK(PEAR_LOG_DEBUG); + $logger->setMask($mask); + +For convenience, two special masks are predefined: ``PEAR_LOG_NONE`` and +``PEAR_LOG_ALL``. ``PEAR_LOG_ALL`` is especially useful for excluding only +specific priorities:: + + $mask = PEAR_LOG_ALL ^ Log::MASK(PEAR_LOG_NOTICE); + $logger->setMask($mask); + +It is also possible to retrieve and modify a Log object's existing mask:: + + $mask = $logger->getMask() | Log::MASK(PEAR_LOG_INFO); + $logger->setMask($mask); + + +Flushing Log Events +------------------- +Some log handlers (such as `the console handler`_) support explicit +"buffering". When buffering is enabled, log events won't actually be written +to the output stream until the handler is closed. Other handlers (such as +`the file handler`_) support implicit buffering because they use the operating +system's IO routines, which may buffer the output. + +It's possible to force these handlers to flush their output, however, by +calling their ``flush()`` method:: + + $conf = array('buffering' => true); + $logger = &Log::singleton('console', '', 'test', $conf); + + for ($i = 0; $i < 10; $i++) { + $logger->log('This event will be buffered.'); + } + + /* Flush all of the buffered log events. */ + $logger->flush(); + + for ($i = 0; $i < 10; $i++) { + $logger->log('This event will be buffered.'); + } + + /* Implicitly flush the buffered events on close. */ + $logger->close(); + +At this time, the ``flush()`` method is only implemented by `the console +handler`_, `the file handler`_, `the Firebug handler`_, and `the mail +handler`_. + + +Standard Log Handlers +===================== + +The Console Handler +------------------- +The Console handler outputs log events directly to the console. It supports +output buffering and configurable string formats. + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``stream`` | File | STDOUT_ | The output stream to use. | ++-------------------+-----------+---------------+---------------------------+ +| ``buffering`` | Boolean | False | Should the output be | +| | | | buffered until shutdown? | ++-------------------+-----------+---------------+---------------------------+ +| ``lineFormat`` | String | ``%1$s %2$s | Log line format | +| | | [%3$s] %4$s`` | specification. | ++-------------------+-----------+---------------+---------------------------+ +| ``timeFormat`` | String | ``%b %d | Time stamp format | +| | | %H:%M:%S`` | (for strftime_). | ++-------------------+-----------+---------------+---------------------------+ + +.. _STDOUT: http://www.php.net/wrappers.php +.. _strftime: http://www.php.net/strftime + +Example +~~~~~~~ +:: + + $logger = &Log::singleton('console', '', 'ident'); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + + +The Display Handler +------------------- +The Display handler simply prints the log events back to the browser. It +respects the ``error_prepend_string`` and ``error_append_string`` `error +handling values`_ and is useful when `logging from standard error handlers`_. + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``lineFormat`` | String | ``%3$s:| Log line format | +| | | %4$s`` | specification. | ++-------------------+-----------+---------------+---------------------------+ +| ``timeFormat`` | String | ``%b %d | Time stamp format | +| | | %H:%M:%S`` | (for strftime_). | ++-------------------+-----------+---------------+---------------------------+ +| ``error_prepend`` | String | PHP INI value | This string will be | +| | | | prepended to the line | +| | | | format. | ++-------------------+-----------+---------------+---------------------------+ +| ``error_append`` | String | PHP INI value | This string will be | +| | | | appended to the line | +| | | | format. | ++-------------------+-----------+---------------+---------------------------+ +| ``linebreak`` | String | ``
\n`` | This string is used to | +| | | | represent a line break. | ++-------------------+-----------+---------------+---------------------------+ + +.. _error handling values: http://www.php.net/errorfunc + +Example +~~~~~~~ +:: + + $conf = array('error_prepend' => '', + 'error_append' => ''); + $logger = &Log::singleton('display', '', '', $conf, PEAR_LOG_DEBUG); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + + +The Error_Log Handler +--------------------- +The Error_Log handler sends log events to PHP's `error_log()`_ function. + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``destination`` | String | '' `(empty)` | Optional destination value| +| | | | for `error_log()`_. See | +| | | | `Error_Log Types`_ for | +| | | | more details. | ++-------------------+-----------+---------------+---------------------------+ +| ``extra_headers`` | String | '' `(empty)` | Additional headers to pass| +| | | | to the `mail()`_ function | +| | | | when the | +| | | | ``PEAR_LOG_TYPE_MAIL`` | +| | | | type is specified. | ++-------------------+-----------+---------------+---------------------------+ + +Error_Log Types +~~~~~~~~~~~~~~~ +All of the available log types are detailed in the `error_log()`_ section of +the PHP manual. For your convenience, the Log package also defines the +following constants that can be used for the ``$name`` handler construction +parameter. + ++---------------------------+-----------------------------------------------+ +| Constant | Description | ++===========================+===============================================+ +| ``PEAR_LOG_TYPE_SYSTEM`` | Log events are sent to PHP's system logger, | +| | which uses the operating system's logging | +| | mechanism or a file (depending on the value | +| | of the `error_log configuration directive`_). | ++---------------------------+-----------------------------------------------+ +| ``PEAR_LOG_TYPE_MAIL`` | Log events are sent via email to the address | +| | specified in the ``destination`` value. | ++---------------------------+-----------------------------------------------+ +| ``PEAR_LOG_TYPE_DEBUG`` | Log events are sent through PHP's debugging | +| | connection. This will only work if | +| | `remote debugging`_ has been enabled. The | +| | ``destination`` value is used to specify the | +| | host name or IP address of the target socket. | ++---------------------------+-----------------------------------------------+ +| ``PEAR_LOG_TYPE_FILE`` | Log events will be appended to the file named | +| | by the ``destination`` value. | ++---------------------------+-----------------------------------------------+ + +.. _error_log(): http://www.php.net/error_log +.. _mail(): http://www.php.net/mail +.. _error_log configuration directive: http://www.php.net/errorfunc#ini.error-log +.. _remote debugging: http://www.php.net/install.configure#install.configure.enable-debugger + +Example +~~~~~~~ +:: + + $logger = &Log::singleton('error_log', PEAR_LOG_TYPE_SYSTEM, 'ident'); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + + +The File Handler +---------------- +The File handler writes log events to a text file using configurable string +formats. + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``append`` | Boolean | True | Should new log entries be | +| | | | append to an existing log | +| | | | file, or should the a new | +| | | | log file overwrite an | +| | | | existing one? | ++-------------------+-----------+---------------+---------------------------+ +| ``locking`` | Boolean | False | Should advisory file | +| | | | locking (using flock_) be | +| | | | used? | ++-------------------+-----------+---------------+---------------------------+ +| ``mode`` | Integer | 0644 | Octal representation of | +| | | | the log file's permissions| +| | | | mode. | ++-------------------+-----------+---------------+---------------------------+ +| ``dirmode`` | Integer | 0755 | Octal representation of | +| | | | the file permission mode | +| | | | that will be used when | +| | | | creating directories that | +| | | | do not already exist. | ++-------------------+-----------+---------------+---------------------------+ +| ``eol`` | String | OS default | The end-on-line character | +| | | | sequence. | ++-------------------+-----------+---------------+---------------------------+ +| ``lineFormat`` | String | ``%1$s %2$s | Log line format | +| | | [%3$s] %4$s`` | specification. | ++-------------------+-----------+---------------+---------------------------+ +| ``timeFormat`` | String | ``%b %d | Time stamp format | +| | | %H:%M:%S`` | (for strftime_). | ++-------------------+-----------+---------------+---------------------------+ + +.. _flock: http://www.php.net/flock +.. _strftime: http://www.php.net/strftime + +The file handler will only attempt to set the ``mode`` value if it was +responsible for creating the file. + +Example +~~~~~~~ +:: + + $conf = array('mode' => 0600, 'timeFormat' => '%X %x'); + $logger = &Log::singleton('file', 'out.log', 'ident', $conf); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + + +The Firebug Handler +------------------- +The Firebug handler outputs log events to the Firebug_ console. It supports +output buffering and configurable string formats. + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``buffering`` | Boolean | False | Should the output be | +| | | | buffered until shutdown? | ++-------------------+-----------+---------------+---------------------------+ +| ``lineFormat`` | String | ``%2$s [%3$s] | Log line format | +| | | %4$s`` | specification. | ++-------------------+-----------+---------------+---------------------------+ +| ``timeFormat`` | String | ``%b %d | Time stamp format | +| | | %H:%M:%S`` | (for strftime_). | ++-------------------+-----------+---------------+---------------------------+ + +.. _Firebug: http://www.getfirebug.com/ +.. _strftime: http://www.php.net/strftime + +Example +~~~~~~~ +:: + + $logger = &Log::singleton('firebug', '', 'ident'); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + + +The Mail Handler +---------------- + +The Mail handler aggregates a session's log events and sends them in the body +of an email message using either the `PEAR Mail`_ package or PHP's native +`mail()`_ function. + +If an empty ``mailBackend`` value is specified, the `mail()`_ function will be +used instead of the `PEAR Mail`_ package. + +Multiple recipients can be specified by separating their email addresses with +commas in the ``$name`` construction parameter. + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``from`` | String | sendmail_from | Value for the message's | +| | | INI value | ``From:`` header. | ++-------------------+-----------+---------------+---------------------------+ +| ``subject`` | String | ``[Log_mail] | Value for the message's | +| | | Log message`` | ``Subject:`` header. | ++-------------------+-----------+---------------+---------------------------+ +| ``preamble`` | String | `` `(empty)` | Preamble for the message. | ++-------------------+-----------+---------------+---------------------------+ +| ``lineFormat`` | String | ``%1$s %2$s | Log line format | +| | | [%3$s] %4$s`` | specification. | ++-------------------+-----------+---------------+---------------------------+ +| ``timeFormat`` | String | ``%b %d | Time stamp format | +| | | %H:%M:%S`` | (for strftime_). | ++-------------------+-----------+---------------+---------------------------+ +| ``mailBackend`` | String | `` `(empty)` | Name of the Mail package | +| | | | backend to use. | ++-------------------+-----------+---------------+---------------------------+ +| ``mailParams`` | Array | `(empty)` | Array of parameters that | +| | | | will be passed to the | +| | | | Mail package backend. | ++-------------------+-----------+---------------+---------------------------+ + +.. _PEAR Mail: http://pear.php.net/package/Mail +.. _mail(): http://www.php.net/mail + +Example +~~~~~~~ +:: + + $conf = array('subject' => 'Important Log Events'); + $logger = &Log::singleton('mail', 'webmaster@example.com', 'ident', $conf); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + + +The MDB2 Handler +---------------- +The MDB2 handler is similar to `the SQL (DB) handler`_, but instead of using +the PEAR DB package, it uses the `MDB2 database abstraction package`_. + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``dsn`` | Mixed | '' `(empty)` | A `Data Source Name`_. | +| | | | |required| | ++-------------------+-----------+---------------+---------------------------+ +| ``options`` | Array | ``persistent``| An array of `MDB2`_ | +| | | | options. | ++-------------------+-----------+---------------+---------------------------+ +| ``db`` | Object | NULL | An existing `MDB2`_ | +| | | | object. If specified, | +| | | | this object will be used, | +| | | | and ``dsn`` will be | +| | | | ignored. | ++-------------------+-----------+---------------+---------------------------+ +| ``sequence`` | String | ``log_id`` | The name of the sequence | +| | | | to use when generating | +| | | | unique event IDs. Under | +| | | | many databases, this will | +| | | | be used as the name of | +| | | | the sequence table. | ++-------------------+-----------+---------------+---------------------------+ +| ``identLimit`` | Integer | 16 | The maximum length of the | +| | | | ``ident`` string. | +| | | | **Changing this value may | +| | | | require updates to the SQL| +| | | | schema, as well.** | ++-------------------+-----------+---------------+---------------------------+ +| ``singleton`` | Boolean | false | Is true, use a singleton | +| | | | database object using | +| | | | `MDB2::singleton()`_. | ++-------------------+-----------+---------------+---------------------------+ + +.. _MDB2: http://pear.php.net/package/MDB2 +.. _MDB2 database abstraction package: MDB2_ +.. _MDB2::singleton(): http://pear.php.net/package/MDB2/docs/latest/MDB2/MDB2.html#methodsingleton + +The Null Handler +---------------- +The Null handler simply consumes log events (akin to sending them to +``/dev/null``). `Log level masks`_ are respected, and the event will still be +sent to any registered `log observers`_. + +Example +~~~~~~~ +:: + + $logger = &Log::singleton('null'); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + + +The SQL (DB) Handler +-------------------- + +The SQL handler sends log events to a database using `PEAR's DB abstraction +layer`_. + +**Note:** Due to the constraints of the default database schema, the SQL +handler limits the length of the ``$ident`` string to sixteen (16) characters. +This limit can be adjusted using the ``identLimit`` configuration parameter. + +The Log Table +~~~~~~~~~~~~~ +The default SQL table used by this handler looks like this:: + + CREATE TABLE log_table ( + id INT NOT NULL, + logtime TIMESTAMP NOT NULL, + ident CHAR(16) NOT NULL, + priority INT NOT NULL, + message VARCHAR(200), + PRIMARY KEY (id) + ); + +This is the "lowest common denominator" that should work across all SQL +compliant database. You may want to make database- or site-specific changes +to this schema to support your specific needs, however. For example, +`PostgreSQL`_ users may prefer to use a ``TEXT`` type for the ``message`` +field. + +.. _PostgreSQL: http://www.postgresql.org/ + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``dsn`` | Mixed | '' `(empty)` | A `Data Source Name`_. | +| | | | |required| | ++-------------------+-----------+---------------+---------------------------+ +| ``sql`` | String | |sql-default| | SQL insertion statement. | ++-------------------+-----------+---------------+---------------------------+ +| ``options`` | Array | ``persistent``| An array of `DB`_ options.| ++-------------------+-----------+---------------+---------------------------+ +| ``db`` | Object | NULL | An existing `DB`_ object. | +| | | | If specified, this object | +| | | | will be used, and ``dsn`` | +| | | | will be ignored. | ++-------------------+-----------+---------------+---------------------------+ +| ``sequence`` | String | ``log_id`` | The name of the sequence | +| | | | to use when generating | +| | | | unique event IDs. Under | +| | | | many databases, this will | +| | | | be used as the name of | +| | | | the sequence table. | ++-------------------+-----------+---------------+---------------------------+ +| ``identLimit`` | Integer | 16 | The maximum length of the | +| | | | ``ident`` string. | +| | | | **Changing this value may | +| | | | require updates to the SQL| +| | | | schema, as well.** | ++-------------------+-----------+---------------+---------------------------+ + +The name of the database table to which the log entries will be written is +specified using the ``$name`` construction parameter (see `Configuring a +Handler`_). + +.. |sql-default| replace:: ``INSERT INTO $table (id, logtime, ident, priority, message) VALUES(?, CURRENT_TIMESTAMP, ?, ?, ?)`` + +.. _DB: http://pear.php.net/package/DB +.. _PEAR's DB abstraction layer: DB_ +.. _Data Source Name: http://pear.php.net/manual/en/package.database.db.intro-dsn.php + +Examples +~~~~~~~~ +Using a `Data Source Name`_ to create a new database connection:: + + $conf = array('dsn' => 'pgsql://jon@localhost+unix/logs'); + $logger = &Log::singleton('sql', 'log_table', 'ident', $conf); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + +Using an existing `DB`_ object:: + + require_once 'DB.php'; + $db = &DB::connect('pgsql://jon@localhost+unix/logs'); + + $conf['db'] = $db; + $logger = &Log::singleton('sql', 'log_table', 'ident', $conf); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + + +The Sqlite Handler +------------------ +:Author: Bertrand Mansion + +The Sqlite handler sends log events to an Sqlite database using the `native +PHP sqlite functions`_. + +It is faster than `the SQL (DB) handler`_ because requests are made directly +to the database without using an abstraction layer. It is also interesting to +note that Sqlite database files can be moved, copied, and deleted on your +system just like any other files, which makes log management easier. Last but +not least, using a database to log your events allows you to use SQL queries +to create reports and statistics. + +When using a database and logging a lot of events, it is recommended to split +the database into smaller databases. This is allowed by Sqlite, and you can +later use the Sqlite `ATTACH`_ statement to query your log database files +globally. + +If the database does not exist when the log is opened, sqlite will try to +create it automatically. If the log table does not exist, it will also be +automatically created. The table creation uses the following SQL request:: + + CREATE TABLE log_table ( + id INTEGER PRIMARY KEY NOT NULL, + logtime NOT NULL, + ident CHAR(16) NOT NULL, + priority INT NOT NULL, + message + ); + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``filename`` | String | '' `(empty)` | Path to an Sqlite | +| | | | database. |required| | ++-------------------+-----------+---------------+---------------------------+ +| ``mode`` | Integer | 0666 | Octal mode used to open | +| | | | the database. | ++-------------------+-----------+---------------+---------------------------+ +| ``persistent`` | Boolean | false | Use a persistent | +| | | | connection. | ++-------------------+-----------+---------------+---------------------------+ + +An already opened database connection can also be passed as parameter instead +of the above configuration. In this case, closing the database connection is +up to the user. + +.. _native PHP sqlite functions: http://www.php.net/sqlite +.. _ATTACH: http://www.sqlite.org/lang.html#attach + +Examples +~~~~~~~~ +Using a configuration to create a new database connection:: + + $conf = array('filename' => 'log.db', 'mode' => 0666, 'persistent' => true); + $logger =& Log::factory('sqlite', 'log_table', 'ident', $conf); + $logger->log('logging an event', PEAR_LOG_WARNING); + +Using an existing connection:: + + $db = sqlite_open('log.db', 0666, $error); + $logger =& Log::factory('sqlite', 'log_table', 'ident', $db); + $logger->log('logging an event', PEAR_LOG_WARNING); + sqlite_close($db); + + +The Syslog Handler +------------------ +The Syslog handler sends log events to the system logging service (syslog on +Unix-like environments or the Event Log on Windows systems). The events are +sent using PHP's `syslog()`_ function. + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``inherit`` | Boolean | false | Inherit the current syslog| +| | | | connection for this | +| | | | process, or start a new | +| | | | one via `openlog()`_? | ++-------------------+-----------+---------------+---------------------------+ + +Facilities +~~~~~~~~~~ ++-------------------+-------------------------------------------------------+ +| Constant | Category Description | ++===================+=======================================================+ +| ``LOG_AUTH`` | Security / authorization messages; ``LOG_AUTHPRIV`` is| +| | preferred on systems where it is defined. | ++-------------------+-------------------------------------------------------+ +| ``LOG_AUTHPRIV`` | Private security / authorization messages | ++-------------------+-------------------------------------------------------+ +| ``LOG_CRON`` | Clock daemon (``cron`` and ``at``) | ++-------------------+-------------------------------------------------------+ +| ``LOG_DAEMON`` | System daemon processes | ++-------------------+-------------------------------------------------------+ +| ``LOG_KERN`` | Kernel messages | ++-------------------+-------------------------------------------------------+ +| ``LOG_LOCAL0`` .. | Reserved for local use; **not** available under | +| ``LOG_LOCAL7`` | Windows. | ++-------------------+-------------------------------------------------------+ +| ``LOG_LPR`` | Printer subsystem | ++-------------------+-------------------------------------------------------+ +| ``LOG_MAIL`` | Mail subsystem | ++-------------------+-------------------------------------------------------+ +| ``LOG_NEWS`` | USENET news subsystem | ++-------------------+-------------------------------------------------------+ +| ``LOG_SYSLOG`` | Internal syslog messages | ++-------------------+-------------------------------------------------------+ +| ``LOG_USER`` | Generic user-level messages | ++-------------------+-------------------------------------------------------+ +| ``LOG_UUCP`` | UUCP subsystem | ++-------------------+-------------------------------------------------------+ + +.. _syslog(): http://www.php.net/syslog +.. _openlog(): http://www.php.net/openlog + +Example +~~~~~~~ +:: + + $logger = &Log::singleton('syslog', LOG_LOCAL0, 'ident'); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + + +The Window Handler +------------------ +The Window handler sends log events to a separate browser window. The +original idea for this handler was inspired by Craig Davis' Zend.com article +entitled `"JavaScript Power PHP Debugging"`_. + +Configuration +~~~~~~~~~~~~~ ++-------------------+-----------+---------------+---------------------------+ +| Parameter | Type | Default | Description | ++===================+===========+===============+===========================+ +| ``title`` | String | ``Log Output | The title of the output | +| | | Window`` | window. | ++-------------------+-----------+---------------+---------------------------+ +| ``styles`` | Array | `ROY G BIV`_ | Mapping of log priorities | +| | | (high to low) | to CSS styles. | ++-------------------+-----------+---------------+---------------------------+ + +**Note:** The Window handler may not work reliably when PHP's `output +buffering`_ system is enabled. + +.. _"JavaScript Power PHP Debugging": http://www.zend.com/zend/tut/tutorial-DebugLib.php +.. _ROY G BIV: http://www.cis.rit.edu/ +.. _output buffering: http://www.php.net/outcontrol + +Example +~~~~~~~ +:: + + $conf = array('title' => 'Sample Log Output'); + $logger = &Log::singleton('win', 'LogWindow', 'ident', $conf); + for ($i = 0; $i < 10; $i++) { + $logger->log("Log entry $i"); + } + + +Composite Handlers +================== +It is often useful to log events to multiple handlers. The Log package +provides a compositing system that marks this task trivial. + +Start by creating the individual log handlers:: + + $console = &Log::singleton('console', '', 'TEST'); + $file = &Log::singleton('file', 'out.log', 'TEST'); + +Then, construct a composite handler and add the individual handlers as +children of the composite:: + + $composite = &Log::singleton('composite'); + $composite->addChild($console); + $composite->addChild($file); + +The composite handler implements the standard ``Log`` interface so you can use +it just like any of the other handlers:: + + $composite->log('This event will be logged to both handlers.'); + +Children can be removed from the composite when they're not longer needed:: + + $composite->removeChild($file); + + +Log Observers +============= +Log observers provide an implementation of the `observer pattern`_. In the +content of the Log package, they provide a mechanism by which you can examine +(i.e. observe) each event as it is logged. This allows the implementation of +special behavior based on the contents of a log event. For example, the +observer code could send an alert email if a log event contained the string +``PANIC``. + +Creating a log observer involves implementing a subclass of the +``Log_observer`` class. The subclass must override the base class's +``notify()`` method. This method is passed a hash containing the event's +priority and event. The subclass's implementation is free to act upon this +information in any way it likes. + +Log observers are attached to ``Log`` instances via the ``attach()`` method:: + + $observer = &Log_observer::factory('yourType'); + $logger->attach($observer); + +Observers can be detached using the ``detach()`` method:: + + $logger->detach($observer); + +At this time, no concrete ``Log_observer`` implementations are distributed +with the Log package. + +.. _observer pattern: http://wikipedia.org/wiki/Observer_pattern + + +Logging From Standard Error Handlers +==================================== + +Logging PHP Errors +------------------ +PHP's default error handler can be overridden using the `set_error_handler()`_ +function. The custom error handling function can use a global Log instance to +log the PHP errors. + +**Note:** Fatal PHP errors cannot be handled by a custom error handler at this +time. + +:: + + function errorHandler($code, $message, $file, $line) + { + global $logger; + + /* Map the PHP error to a Log priority. */ + switch ($code) { + case E_WARNING: + case E_USER_WARNING: + $priority = PEAR_LOG_WARNING; + break; + case E_NOTICE: + case E_USER_NOTICE: + $priority = PEAR_LOG_NOTICE; + break; + case E_ERROR: + case E_USER_ERROR: + $priority = PEAR_LOG_ERR; + break; + default: + $priority = PEAR_LOG_INFO; + } + + $logger->log($message . ' in ' . $file . ' at line ' . $line, + $priority); + } + + set_error_handler('errorHandler'); + trigger_error('This is an information log message.', E_USER_NOTICE); + +.. _set_error_handler(): http://www.php.net/set_error_handler + +Logging PHP Assertions +---------------------- +PHP allows user-defined `assert()`_ callback handlers. The assertion callback +is configured using the `assert_options()`_ function. + +:: + + function assertCallback($file, $line, $message) + { + global $logger; + + $logger->log($message . ' in ' . $file . ' at line ' . $line, + PEAR_LOG_ALERT); + } + + assert_options(ASSERT_CALLBACK, 'assertCallback'); + assert(false); + +.. _assert(): http://www.php.net/assert +.. _assert_options(): http://www.php.net/assert_options + +Logging PHP Exceptions +---------------------- +PHP 5 and later support the concept of `exceptions`_. A custom exception +handler can be assigned using the `set_exception_handler()`_ function. + +:: + + function exceptionHandler($exception) + { + global $logger; + + $logger->log($exception->getMessage(), PEAR_LOG_ALERT); + } + + set_exception_handler('exceptionHandler'); + throw new Exception('Uncaught Exception'); + +.. _exceptions: http://www.php.net/exceptions +.. _set_exception_handler(): http://www.php.net/set_exception_handler + +Logging PEAR Errors +------------------- +The Log package can be used with `PEAR::setErrorHandling()`_'s +``PEAR_ERROR_CALLBACK`` mechanism by writing an error handling function that +uses a global Log instance. Here's an example:: + + function errorHandler($error) + { + global $logger; + + $message = $error->getMessage(); + + if (!empty($error->backtrace[1]['file'])) { + $message .= ' (' . $error->backtrace[1]['file']; + if (!empty($error->backtrace[1]['line'])) { + $message .= ' at line ' . $error->backtrace[1]['line']; + } + $message .= ')'; + } + + $logger->log($message, $error->code); + } + + PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'errorHandler'); + PEAR::raiseError('This is an information log message.', PEAR_LOG_INFO); + +.. _PEAR::setErrorHandling(): http://pear.php.net/manual/en/core.pear.pear.seterrorhandling.php + + +Custom Handlers +=============== +There are times when the standard handlers aren't a perfect match for your +needs. In those situations, the solution might be to write a custom handler. + +Using a Custom Handler +---------------------- +Using a custom Log handler is very simple. Once written (see `Writing New +Handlers`_ and `Extending Existing Handlers`_ below), you have the choice of +placing the file in your PEAR installation's main ``Log/`` directory (usually +something like ``/usr/local/lib/php/Log`` or ``C:\php\pear\Log``), where it +can be found and use by any PHP application on the system, or placing the file +somewhere in your application's local hierarchy and including it before the +the custom Log object is constructed. + +Method 1: Handler in the Standard Location +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +After copying the handler file to your PEAR installation's ``Log/`` directory, +simply treat the handler as if it were part of the standard distributed. If +your handler is named ``custom`` (and therefore implemented by a class named +``Log_custom``):: + + require_once 'Log.php'; + + $logger = &Log::factory('custom', '', 'CUSTOM'); + + +Method 2: Handler in a Custom Location +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you prefer storing your handler in your application's local hierarchy, +you'll need to include that file before you can create a Log instance based on +it. + +:: + + require_once 'Log.php'; + require_once 'LocalHandlers/custom.php'; + + $logger = &Log::factory('custom', '', 'CUSTOM'); + + +Writing New Handlers +-------------------- +Writing a new Log handler is as simple as authoring a new class that extends +the ``Log`` class and that implements a relatively small set of standard +methods. + +Every handler's class name must start with ``Log_`` in order for it to be +recognized by the Log package. + +:: + + class Log_custom extends Log + +The handler's constructor will be called with four parameters. These values +are discussed in detail in the `Configuring a Handler`_ section. + +:: + + Log_custom($name, $ident = '', $conf = array(), $level = PEAR_LOG_DEBUG) + +The constructor is responsible for configuring the handler based on these +values. Handler-specific parameters are passed as part of the ``$conf`` +array. At a minimum, the handler's constructor must set the following values +defined by the ``Log`` base class:: + + $this->_id = md5(microtime()); + $this->_name = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + +The `Handler Methods`_ section below details the various standard methods that +can be implemented by a log handler. The `Utility Methods`_ section describes +some useful utility methods provided by the ``Log`` base class which may be +useful when implementing a log handler. + + +Extending Existing Handlers +--------------------------- +Extending existing handlers is very similar to `writing new handlers`_ with +the exception that, instead of inheriting from the ``Log`` base class +directly, the handler extends an existing handler's class. This is a useful +way of adding some custom behavior to an existing handler without writing an +entirely new class (in the spirit of object-oriented programming). + +For example, `the mail handler`_ could be extended to support sending messages +with MIME-encoded attachments simply by authoring a new ``Log_mail_mime`` +class with a compliant constructor and a custom ``log()`` method. The rest of +the standard methods would fall back on the ``Log_mail`` base class's +implementations. + +Obviously, the specific details involved in extending an existing handler +require a good working understanding of that handler's implementation. + + +Handler Methods +--------------- + +bool open() +~~~~~~~~~~~ +The ``open()`` method is called to open the log resource for output. Handlers +can call ``open()`` immediately upon construction or lazily at runtime +(perhaps when the first log event is received). + +The ``Log`` base class provides a protected ``$_opened`` member variable which +should be set to ``true`` when the log handler is opened and ``false`` when it +is closed. Handler methods can inspect this value to determine whether or not +the handler is currently open and ready for output. + +If the ``open()`` method fails to ready the handler for output, it should +return ``false`` and set ``$this->_opened`` to ``false``. + +bool close() +~~~~~~~~~~~~ +The ``close()`` method is called to close the log resource. This method is +the analog of the ``open()`` method. It should be safe to call ``close()`` +even when the handler is not open for output. + +If the ``close()`` method fails to close the handler, it should return +``false``. Otherwise, it should return ``true``. The ``$this->_opened`` +flag should also be updated appropriately. + +bool flush() +~~~~~~~~~~~~ +The ``flush()`` method flushes any buffered log events, as described in +`Flushing Log Events`_. The implementation of this method will be largely +handler-specific. If the handler does not support buffered output, +implementing this method is not necessary; the ``Log`` class's ``flush()`` +method will be called instead. + +bool log($message, $priority = null) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The ``log()`` method is the core of every log handler. It is called whenever +the user wishes to log an event. The implementation of this method is very +handler-specific. It should return ``true`` or ``false``, depending on +whether or not the message was successfully logged by the handler. + +The ``log()`` implementation should be sure to call `_announce()`__ whenever +an event is successfully logged. + +.. __: void _announce($event) + +Utility Methods +--------------- +These utility methods are provided by the ``Log`` base class and provide +common, useful functionality to handler implementations. + +string _extractMessage($message) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This method returns the string representation of the provided message data. + +If ``$message`` is an object, ``_extractMessage()`` will attempt to extract +the message text using a known method (such as a `PEAR_Error`_ object's +`getMessage()`_ method). If a known method, cannot be found, the serialized +representation of the object will be returned. + +If the message data is already a string, it will be returned unchanged. + +.. _PEAR_Error: http://pear.php.net/manual/en/core.pear.pear-error.php +.. _getMessage(): http://pear.php.net/manual/en/core.pear.pear-error.getmessage.php + +string _format($format, $timestamp, $priority, $message) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This method produces a formatted log line based on a format string and a set +of variables representing the current log record and state. + +bool _isMasked($priority) +~~~~~~~~~~~~~~~~~~~~~~~~~ +This method checks if the given priority is included in the handler's current +level mask. This is useful for determining whether or not a log event should +be written to the handler's log. + +void _announce($event) +~~~~~~~~~~~~~~~~~~~~~~ +This method informs any registered `log observers`_ that a new event has been +logged. ``$event`` is an array containing two named elements:: + + array('priority' => $priority, 'message' => $message) + +``_announce()`` should be called from a handler's `log()`_ method whenever an +event is successfully logged. Otherwise, registered observers will never +become aware of the event. + +.. _log(): bool log($message, $priority = null) + +.. |required| replace:: **[required]** + +.. vim: tabstop=4 shiftwidth=4 softtabstop=4 expandtab textwidth=78 ft=rst: diff --git a/include/PEAR/Log/error_log.php b/include/PEAR/Log/error_log.php new file mode 100644 index 0000000..117e96c --- /dev/null +++ b/include/PEAR/Log/error_log.php @@ -0,0 +1,127 @@ + + * @since Log 1.7.0 + * @package Log + * + * @example error_log.php Using the error_log handler. + */ +class Log_error_log extends Log +{ + /** + * The error_log() log type. + * @var integer + * @access private + */ + var $_type = PEAR_LOG_TYPE_SYSTEM; + + /** + * The type-specific destination value. + * @var string + * @access private + */ + var $_destination = ''; + + /** + * Additional headers to pass to the mail() function when the + * PEAR_LOG_TYPE_MAIL type is used. + * @var string + * @access private + */ + var $_extra_headers = ''; + + /** + * Constructs a new Log_error_log object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_error_log($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_type = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (!empty($conf['destination'])) { + $this->_destination = $conf['destination']; + } + if (!empty($conf['extra_headers'])) { + $this->_extra_headers = $conf['extra_headers']; + } + } + + /** + * Opens the handler. + * + * @access public + * @since Log 1.9.6 + */ + function open() + { + $this->_opened = true; + return true; + } + + /** + * Closes the handler. + * + * @access public + * @since Log 1.9.6 + */ + function close() + { + $this->_opened = false; + return true; + } + + /** + * Logs $message using PHP's error_log() function. The message is also + * passed along to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + $success = error_log($this->_ident . ': ' . $message, $this->_type, + $this->_destination, $this->_extra_headers); + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return $success; + } + +} diff --git a/include/PEAR/Log/file.php b/include/PEAR/Log/file.php new file mode 100644 index 0000000..943eeca --- /dev/null +++ b/include/PEAR/Log/file.php @@ -0,0 +1,316 @@ + + * @author Roman Neuhauser + * @since Log 1.0 + * @package Log + * + * @example file.php Using the file handler. + */ +class Log_file extends Log +{ + /** + * String containing the name of the log file. + * @var string + * @access private + */ + var $_filename = 'php.log'; + + /** + * Handle to the log file. + * @var resource + * @access private + */ + var $_fp = false; + + /** + * Should new log entries be append to an existing log file, or should the + * a new log file overwrite an existing one? + * @var boolean + * @access private + */ + var $_append = true; + + /** + * Should advisory file locking (i.e., flock()) be used? + * @var boolean + * @access private + */ + var $_locking = false; + + /** + * Integer (in octal) containing the log file's permissions mode. + * @var integer + * @access private + */ + var $_mode = 0644; + + /** + * Integer (in octal) specifying the file permission mode that will be + * used when creating directories that do not already exist. + * @var integer + * @access private + */ + var $_dirmode = 0755; + + /** + * String containing the format of a log line. + * @var string + * @access private + */ + var $_lineFormat = '%1$s %2$s [%3$s] %4$s'; + + /** + * String containing the timestamp format. It will be passed directly to + * strftime(). Note that the timestamp string will generated using the + * current locale. + * @var string + * @access private + */ + var $_timeFormat = '%b %d %H:%M:%S'; + + /** + * String containing the end-on-line character sequence. + * @var string + * @access private + */ + var $_eol = "\n"; + + /** + * Constructs a new Log_file object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_file($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_filename = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (isset($conf['append'])) { + $this->_append = $conf['append']; + } + + if (isset($conf['locking'])) { + $this->_locking = $conf['locking']; + } + + if (!empty($conf['mode'])) { + if (is_string($conf['mode'])) { + $this->_mode = octdec($conf['mode']); + } else { + $this->_mode = $conf['mode']; + } + } + + if (!empty($conf['dirmode'])) { + if (is_string($conf['dirmode'])) { + $this->_dirmode = octdec($conf['dirmode']); + } else { + $this->_dirmode = $conf['dirmode']; + } + } + + if (!empty($conf['lineFormat'])) { + $this->_lineFormat = str_replace(array_keys($this->_formatMap), + array_values($this->_formatMap), + $conf['lineFormat']); + } + + if (!empty($conf['timeFormat'])) { + $this->_timeFormat = $conf['timeFormat']; + } + + if (!empty($conf['eol'])) { + $this->_eol = $conf['eol']; + } else { + $this->_eol = (strstr(PHP_OS, 'WIN')) ? "\r\n" : "\n"; + } + + register_shutdown_function(array(&$this, '_Log_file')); + } + + /** + * Destructor + */ + function _Log_file() + { + if ($this->_opened) { + $this->close(); + } + } + + /** + * Creates the given directory path. If the parent directories don't + * already exist, they will be created, too. + * + * This implementation is inspired by Python's os.makedirs function. + * + * @param string $path The full directory path to create. + * @param integer $mode The permissions mode with which the + * directories will be created. + * + * @return True if the full path is successfully created or already + * exists. + * + * @access private + */ + function _mkpath($path, $mode = 0700) + { + /* Separate the last pathname component from the rest of the path. */ + $head = dirname($path); + $tail = basename($path); + + /* Make sure we've split the path into two complete components. */ + if (empty($tail)) { + $head = dirname($path); + $tail = basename($path); + } + + /* Recurse up the path if our current segment does not exist. */ + if (!empty($head) && !empty($tail) && !is_dir($head)) { + $this->_mkpath($head, $mode); + } + + /* Create this segment of the path. */ + return @mkdir($head, $mode); + } + + /** + * Opens the log file for output. If the specified log file does not + * already exist, it will be created. By default, new log entries are + * appended to the end of the log file. + * + * This is implicitly called by log(), if necessary. + * + * @access public + */ + function open() + { + if (!$this->_opened) { + /* If the log file's directory doesn't exist, create it. */ + if (!is_dir(dirname($this->_filename))) { + $this->_mkpath($this->_filename, $this->_dirmode); + } + + /* Determine whether the log file needs to be created. */ + $creating = !file_exists($this->_filename); + + /* Obtain a handle to the log file. */ + $this->_fp = fopen($this->_filename, ($this->_append) ? 'a' : 'w'); + + /* We consider the file "opened" if we have a valid file pointer. */ + $this->_opened = ($this->_fp !== false); + + /* Attempt to set the file's permissions if we just created it. */ + if ($creating && $this->_opened) { + chmod($this->_filename, $this->_mode); + } + } + + return $this->_opened; + } + + /** + * Closes the log file if it is open. + * + * @access public + */ + function close() + { + /* If the log file is open, close it. */ + if ($this->_opened && fclose($this->_fp)) { + $this->_opened = false; + } + + return ($this->_opened === false); + } + + /** + * Flushes all pending data to the file handle. + * + * @access public + * @since Log 1.8.2 + */ + function flush() + { + if (is_resource($this->_fp)) { + return fflush($this->_fp); + } + + return false; + } + + /** + * Logs $message to the output window. The message is also passed along + * to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the log file isn't already open, open it now. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build the string containing the complete log line. */ + $line = $this->_format($this->_lineFormat, + strftime($this->_timeFormat), + $priority, $message) . $this->_eol; + + /* If locking is enabled, acquire an exclusive lock on the file. */ + if ($this->_locking) { + flock($this->_fp, LOCK_EX); + } + + /* Write the log line to the log file. */ + $success = (fwrite($this->_fp, $line) !== false); + + /* Unlock the file now that we're finished writing to it. */ + if ($this->_locking) { + flock($this->_fp, LOCK_UN); + } + + /* Notify observers about this log message. */ + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return $success; + } + +} diff --git a/include/PEAR/Log/firebug.php b/include/PEAR/Log/firebug.php new file mode 100644 index 0000000..1262750 --- /dev/null +++ b/include/PEAR/Log/firebug.php @@ -0,0 +1,214 @@ + + * @since Log 1.9.11 + * @package Log + * + * @example firebug.php Using the firebug handler. + */ +class Log_firebug extends Log +{ + /** + * Should the output be buffered or displayed immediately? + * @var string + * @access private + */ + var $_buffering = false; + + /** + * String holding the buffered output. + * @var string + * @access private + */ + var $_buffer = array(); + + /** + * String containing the format of a log line. + * @var string + * @access private + */ + var $_lineFormat = '%2$s [%3$s] %4$s'; + + /** + * String containing the timestamp format. It will be passed directly to + * strftime(). Note that the timestamp string will generated using the + * current locale. + * + * Note! Default lineFormat of this driver does not display time. + * + * @var string + * @access private + */ + var $_timeFormat = '%b %d %H:%M:%S'; + + /** + * Mapping of log priorities to Firebug methods. + * @var array + * @access private + */ + var $_methods = array( + PEAR_LOG_EMERG => 'error', + PEAR_LOG_ALERT => 'error', + PEAR_LOG_CRIT => 'error', + PEAR_LOG_ERR => 'error', + PEAR_LOG_WARNING => 'warn', + PEAR_LOG_NOTICE => 'info', + PEAR_LOG_INFO => 'info', + PEAR_LOG_DEBUG => 'debug' + ); + + /** + * Constructs a new Log_firebug object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_firebug($name = '', $ident = 'PHP', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + if (isset($conf['buffering'])) { + $this->_buffering = $conf['buffering']; + } + + if ($this->_buffering) { + register_shutdown_function(array(&$this, '_Log_firebug')); + } + + if (!empty($conf['lineFormat'])) { + $this->_lineFormat = str_replace(array_keys($this->_formatMap), + array_values($this->_formatMap), + $conf['lineFormat']); + } + + if (!empty($conf['timeFormat'])) { + $this->_timeFormat = $conf['timeFormat']; + } + } + + /** + * Opens the firebug handler. + * + * @access public + */ + function open() + { + $this->_opened = true; + return true; + } + + /** + * Destructor + */ + function _Log_firebug() + { + $this->close(); + } + + /** + * Closes the firebug handler. + * + * @access public + */ + function close() + { + $this->flush(); + $this->_opened = false; + return true; + } + + /** + * Flushes all pending ("buffered") data. + * + * @access public + */ + function flush() { + if (count($this->_buffer)) { + print '\n"; + }; + $this->_buffer = array(); + } + + /** + * Writes $message to Firebug console. Also, passes the message + * along to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + $method = $this->_methods[$priority]; + + /* normalize line breaks */ + $message = str_replace("\r\n", "\n", $message); + + /* escape line breaks */ + $message = str_replace("\n", "\\n\\\n", $message); + + /* escape quotes */ + $message = str_replace('"', '\\"', $message); + + /* Build the string containing the complete log line. */ + $line = $this->_format($this->_lineFormat, + strftime($this->_timeFormat), + $priority, + $message); + + if ($this->_buffering) { + $this->_buffer[] = sprintf('console.%s("%s");', $method, $line); + } else { + print '\n"; + } + /* Notify observers about this log message. */ + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/include/PEAR/Log/mail.php b/include/PEAR/Log/mail.php new file mode 100644 index 0000000..6e977c2 --- /dev/null +++ b/include/PEAR/Log/mail.php @@ -0,0 +1,294 @@ + + * @author Jon Parise + * @since Log 1.3 + * @package Log + * + * @example mail.php Using the mail handler. + */ +class Log_mail extends Log +{ + /** + * String holding the recipients' email addresses. Multiple addresses + * should be separated with commas. + * @var string + * @access private + */ + var $_recipients = ''; + + /** + * String holding the sender's email address. + * @var string + * @access private + */ + var $_from = ''; + + /** + * String holding the email's subject. + * @var string + * @access private + */ + var $_subject = '[Log_mail] Log message'; + + /** + * String holding an optional preamble for the log messages. + * @var string + * @access private + */ + var $_preamble = ''; + + /** + * String containing the format of a log line. + * @var string + * @access private + */ + var $_lineFormat = '%1$s %2$s [%3$s] %4$s'; + + /** + * String containing the timestamp format. It will be passed directly to + * strftime(). Note that the timestamp string will generated using the + * current locale. + * @var string + * @access private + */ + var $_timeFormat = '%b %d %H:%M:%S'; + + /** + * String holding the mail message body. + * @var string + * @access private + */ + var $_message = ''; + + /** + * Flag used to indicated that log lines have been written to the message + * body and the message should be sent on close(). + * @var boolean + * @access private + */ + var $_shouldSend = false; + + /** + * String holding the backend name of PEAR::Mail + * @var string + * @access private + */ + var $_mailBackend = ''; + + /** + * Array holding the params for PEAR::Mail + * @var array + * @access private + */ + var $_mailParams = array(); + + /** + * Constructs a new Log_mail object. + * + * Here is how you can customize the mail driver with the conf[] hash : + * $conf['from']: the mail's "From" header line, + * $conf['subject']: the mail's "Subject" line. + * $conf['mailBackend']: backend name of PEAR::Mail + * $conf['mailParams']: parameters for the PEAR::Mail backend + * + * @param string $name The message's recipients. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_mail($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_recipients = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (!empty($conf['from'])) { + $this->_from = $conf['from']; + } else { + $this->_from = ini_get('sendmail_from'); + } + + if (!empty($conf['subject'])) { + $this->_subject = $conf['subject']; + } + + if (!empty($conf['preamble'])) { + $this->_preamble = $conf['preamble']; + } + + if (!empty($conf['lineFormat'])) { + $this->_lineFormat = str_replace(array_keys($this->_formatMap), + array_values($this->_formatMap), + $conf['lineFormat']); + } + + if (!empty($conf['timeFormat'])) { + $this->_timeFormat = $conf['timeFormat']; + } + + if (!empty($conf['mailBackend'])) { + $this->_mailBackend = $conf['mailBackend']; + } + + if (!empty($conf['mailParams'])) { + $this->_mailParams = $conf['mailParams']; + } + + /* register the destructor */ + register_shutdown_function(array(&$this, '_Log_mail')); + } + + /** + * Destructor. Calls close(). + * + * @access private + */ + function _Log_mail() + { + $this->close(); + } + + /** + * Starts a new mail message. + * This is implicitly called by log(), if necessary. + * + * @access public + */ + function open() + { + if (!$this->_opened) { + if (!empty($this->_preamble)) { + $this->_message = $this->_preamble . "\r\n\r\n"; + } + $this->_opened = true; + $_shouldSend = false; + } + + return $this->_opened; + } + + /** + * Closes the message, if it is open, and sends the mail. + * This is implicitly called by the destructor, if necessary. + * + * @access public + */ + function close() + { + if ($this->_opened) { + if ($this->_shouldSend && !empty($this->_message)) { + if ($this->_mailBackend === '') { // use mail() + $headers = "From: $this->_from\r\n"; + $headers .= "User-Agent: Log_mail"; + if (mail($this->_recipients, $this->_subject, + $this->_message, $headers) == false) { + return false; + } + } else { // use PEAR::Mail + include_once 'Mail.php'; + $headers = array('From' => $this->_from, + 'To' => $this->_recipients, + 'User-Agent' => 'Log_mail', + 'Subject' => $this->_subject); + $mailer = &Mail::factory($this->_mailBackend, + $this->_mailParams); + $res = $mailer->send($this->_recipients, $headers, + $this->_message); + if (PEAR::isError($res)) { + return false; + } + } + + /* Clear the message string now that the email has been sent. */ + $this->_message = ''; + $this->_shouldSend = false; + } + $this->_opened = false; + } + + return ($this->_opened === false); + } + + /** + * Flushes the log output by forcing the email message to be sent now. + * Events that are logged after flush() is called will be appended to a + * new email message. + * + * @access public + * @since Log 1.8.2 + */ + function flush() + { + /* + * It's sufficient to simply call close() to flush the output. + * The next call to log() will cause the handler to be reopened. + */ + return $this->close(); + } + + /** + * Writes $message to the currently open mail message. + * Calls open(), if necessary. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the message isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Append the string containing the complete log line. */ + $this->_message .= $this->_format($this->_lineFormat, + strftime($this->_timeFormat), + $priority, $message) . "\r\n"; + $this->_shouldSend = true; + + /* Notify observers about this log message. */ + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } +} diff --git a/include/PEAR/Log/mcal.php b/include/PEAR/Log/mcal.php new file mode 100644 index 0000000..ab88d45 --- /dev/null +++ b/include/PEAR/Log/mcal.php @@ -0,0 +1,170 @@ + + * @since Horde 1.3 + * @since Log 1.0 + * @package Log + */ +class Log_mcal extends Log +{ + /** + * holding the calendar specification to connect to. + * @var string + * @access private + */ + var $_calendar = '{localhost/mstore}'; + + /** + * holding the username to use. + * @var string + * @access private + */ + var $_username = ''; + + /** + * holding the password to use. + * @var string + * @access private + */ + var $_password = ''; + + /** + * holding the options to pass to the calendar stream. + * @var integer + * @access private + */ + var $_options = 0; + + /** + * ResourceID of the MCAL stream. + * @var string + * @access private + */ + var $_stream = ''; + + /** + * Integer holding the log facility to use. + * @var string + * @access private + */ + var $_name = LOG_SYSLOG; + + + /** + * Constructs a new Log_mcal object. + * + * @param string $name The category to use for our events. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_mcal($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_name = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + $this->_calendar = $conf['calendar']; + $this->_username = $conf['username']; + $this->_password = $conf['password']; + $this->_options = $conf['options']; + } + + /** + * Opens a calendar stream, if it has not already been + * opened. This is implicitly called by log(), if necessary. + * @access public + */ + function open() + { + if (!$this->_opened) { + $this->_stream = mcal_open($this->_calendar, $this->_username, + $this->_password, $this->_options); + $this->_opened = true; + } + + return $this->_opened; + } + + /** + * Closes the calendar stream, if it is open. + * @access public + */ + function close() + { + if ($this->_opened) { + mcal_close($this->_stream); + $this->_opened = false; + } + + return ($this->_opened === false); + } + + /** + * Logs $message and associated information to the currently open + * calendar stream. Calls open() if necessary. Also passes the + * message along to any Log_observer instances that are observing + * this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + $date_str = date('Y:n:j:G:i:s'); + $dates = explode(':', $date_str); + + mcal_event_init($this->_stream); + mcal_event_set_title($this->_stream, $this->_ident); + mcal_event_set_category($this->_stream, $this->_name); + mcal_event_set_description($this->_stream, $message); + mcal_event_add_attribute($this->_stream, 'priority', $priority); + mcal_event_set_start($this->_stream, $dates[0], $dates[1], $dates[2], + $dates[3], $dates[4], $dates[5]); + mcal_event_set_end($this->_stream, $dates[0], $dates[1], $dates[2], + $dates[3], $dates[4], $dates[5]); + mcal_append_event($this->_stream); + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/include/PEAR/Log/mdb2.php b/include/PEAR/Log/mdb2.php new file mode 100644 index 0000000..cc06787 --- /dev/null +++ b/include/PEAR/Log/mdb2.php @@ -0,0 +1,358 @@ + + * @author Jon Parise + * @since Log 1.9.0 + * @package Log + */ +class Log_mdb2 extends Log +{ + /** + * Variable containing the DSN information. + * @var mixed + * @access private + */ + var $_dsn = ''; + + /** + * Array containing our set of DB configuration options. + * @var array + * @access private + */ + var $_options = array('persistent' => true); + + /** + * Object holding the database handle. + * @var object + * @access private + */ + var $_db = null; + + /** + * Resource holding the prepared statement handle. + * @var resource + * @access private + */ + var $_statement = null; + + /** + * Flag indicating that we're using an existing database connection. + * @var boolean + * @access private + */ + var $_existingConnection = false; + + /** + * String holding the database table to use. + * @var string + * @access private + */ + var $_table = 'log_table'; + + /** + * String holding the name of the ID sequence. + * @var string + * @access private + */ + var $_sequence = 'log_id'; + + /** + * Maximum length of the $ident string. This corresponds to the size of + * the 'ident' column in the SQL table. + * @var integer + * @access private + */ + var $_identLimit = 16; + + /** + * Set of field types used in the database table. + * @var array + * @access private + */ + var $_types = array( + 'id' => 'integer', + 'logtime' => 'timestamp', + 'ident' => 'text', + 'priority' => 'text', + 'message' => 'clob' + ); + + /** + * Constructs a new sql logging object. + * + * @param string $name The target SQL table. + * @param string $ident The identification field. + * @param array $conf The connection configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_mdb2($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_table = $name; + $this->_mask = Log::UPTO($level); + + /* If an options array was provided, use it. */ + if (isset($conf['options']) && is_array($conf['options'])) { + $this->_options = $conf['options']; + } + + /* If a specific sequence name was provided, use it. */ + if (!empty($conf['sequence'])) { + $this->_sequence = $conf['sequence']; + } + + /* If a specific sequence name was provided, use it. */ + if (isset($conf['identLimit'])) { + $this->_identLimit = $conf['identLimit']; + } + + /* Now that the ident limit is confirmed, set the ident string. */ + $this->setIdent($ident); + + /* If an existing database connection was provided, use it. */ + if (isset($conf['db'])) { + $this->_db = &$conf['db']; + $this->_existingConnection = true; + $this->_opened = true; + } elseif (isset($conf['singleton'])) { + $this->_db = &MDB2::singleton($conf['singleton'], $this->_options); + $this->_existingConnection = true; + $this->_opened = true; + } else { + $this->_dsn = $conf['dsn']; + } + } + + /** + * Opens a connection to the database, if it has not already + * been opened. This is implicitly called by log(), if necessary. + * + * @return boolean True on success, false on failure. + * @access public + */ + function open() + { + if (!$this->_opened) { + /* Use the DSN and options to create a database connection. */ + $this->_db = &MDB2::connect($this->_dsn, $this->_options); + if (PEAR::isError($this->_db)) { + return false; + } + + /* Create a prepared statement for repeated use in log(). */ + if (!$this->_prepareStatement()) { + return false; + } + + /* We now consider out connection open. */ + $this->_opened = true; + } + + return $this->_opened; + } + + /** + * Closes the connection to the database if it is still open and we were + * the ones that opened it. It is the caller's responsible to close an + * existing connection that was passed to us via $conf['db']. + * + * @return boolean True on success, false on failure. + * @access public + */ + function close() + { + /* If we have a statement object, free it. */ + if (is_object($this->_statement)) { + $this->_statement->free(); + $this->_statement = null; + } + + /* If we opened the database connection, disconnect it. */ + if ($this->_opened && !$this->_existingConnection) { + $this->_opened = false; + return $this->_db->disconnect(); + } + + return ($this->_opened === false); + } + + /** + * Sets this Log instance's identification string. Note that this + * SQL-specific implementation will limit the length of the $ident string + * to sixteen (16) characters. + * + * @param string $ident The new identification string. + * + * @access public + * @since Log 1.8.5 + */ + function setIdent($ident) + { + $this->_ident = substr($ident, 0, $this->_identLimit); + } + + /** + * Inserts $message to the currently open database. Calls open(), + * if necessary. Also passes the message along to any Log_observer + * instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* If we don't already have a statement object, create one. */ + if (!is_object($this->_statement) && !$this->_prepareStatement()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build our set of values for this log entry. */ + $values = array( + 'id' => $this->_db->nextId($this->_sequence), + 'logtime' => MDB2_Date::mdbNow(), + 'ident' => $this->_ident, + 'priority' => $priority, + 'message' => $message + ); + + /* Execute the SQL query for this log entry insertion. */ + $this->_db->expectError(MDB2_ERROR_NOSUCHTABLE); + $result = &$this->_statement->execute($values); + $this->_db->popExpect(); + + /* Attempt to handle any errors. */ + if (PEAR::isError($result)) { + /* We can only handle MDB2_ERROR_NOSUCHTABLE errors. */ + if ($result->getCode() != MDB2_ERROR_NOSUCHTABLE) { + return false; + } + + /* Attempt to create the target table. */ + if (!$this->_createTable()) { + return false; + } + + /* Recreate our prepared statement resource. */ + $this->_statement->free(); + if (!$this->_prepareStatement()) { + return false; + } + + /* Attempt to re-execute the insertion query. */ + $result = $this->_statement->execute($values); + if (PEAR::isError($result)) { + return false; + } + } + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + + /** + * Create the log table in the database. + * + * @return boolean True on success or false on failure. + * @access private + */ + function _createTable() + { + $this->_db->loadModule('Manager', null, true); + $result = $this->_db->manager->createTable( + $this->_table, + array( + 'id' => array('type' => $this->_types['id']), + 'logtime' => array('type' => $this->_types['logtime']), + 'ident' => array('type' => $this->_types['ident']), + 'priority' => array('type' => $this->_types['priority']), + 'message' => array('type' => $this->_types['message']) + ) + ); + if (PEAR::isError($result)) { + return false; + } + + $result = $this->_db->manager->createIndex( + $this->_table, + 'unique_id', + array('fields' => array('id' => true), 'unique' => true) + ); + if (PEAR::isError($result)) { + return false; + } + + return true; + } + + /** + * Prepare the SQL insertion statement. + * + * @return boolean True if the statement was successfully created. + * + * @access private + * @since Log 1.9.0 + */ + function _prepareStatement() + { + $this->_statement = &$this->_db->prepare( + 'INSERT INTO ' . $this->_table . + ' (id, logtime, ident, priority, message)' . + ' VALUES(:id, :logtime, :ident, :priority, :message)', + $this->_types, MDB2_PREPARE_MANIP); + + /* Return success if we didn't generate an error. */ + return (PEAR::isError($this->_statement) === false); + } +} diff --git a/include/PEAR/Log/null.php b/include/PEAR/Log/null.php new file mode 100644 index 0000000..58cb9b4 --- /dev/null +++ b/include/PEAR/Log/null.php @@ -0,0 +1,91 @@ + + * @since Log 1.8.2 + * @package Log + * + * @example null.php Using the null handler. + */ +class Log_null extends Log +{ + /** + * Constructs a new Log_null object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_null($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + } + + /** + * Opens the handler. + * + * @access public + * @since Log 1.9.6 + */ + function open() + { + $this->_opened = true; + return true; + } + + /** + * Closes the handler. + * + * @access public + * @since Log 1.9.6 + */ + function close() + { + $this->_opened = false; + return true; + } + + /** + * Simply consumes the log event. The message will still be passed + * along to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/include/PEAR/Log/observer.php b/include/PEAR/Log/observer.php new file mode 100644 index 0000000..d167ca9 --- /dev/null +++ b/include/PEAR/Log/observer.php @@ -0,0 +1,129 @@ + + * @since Horde 1.3 + * @since Log 1.0 + * @package Log + * + * @example observer_mail.php An example Log_observer implementation. + */ +class Log_observer +{ + /** + * Instance-specific unique identification number. + * + * @var integer + * @access private + */ + var $_id = 0; + + /** + * The minimum priority level of message that we want to hear about. + * PEAR_LOG_EMERG is the highest priority, so we will only hear messages + * with an integer priority value less than or equal to ours. It defaults + * to PEAR_LOG_INFO, which listens to everything except PEAR_LOG_DEBUG. + * + * @var string + * @access private + */ + var $_priority = PEAR_LOG_INFO; + + /** + * Creates a new basic Log_observer instance. + * + * @param integer $priority The highest priority at which to receive + * log event notifications. + * + * @access public + */ + function Log_observer($priority = PEAR_LOG_INFO) + { + $this->_id = md5(microtime()); + $this->_priority = $priority; + } + + /** + * Attempts to return a new concrete Log_observer instance of the requested + * type. + * + * @param string $type The type of concreate Log_observer subclass + * to return. + * @param integer $priority The highest priority at which to receive + * log event notifications. + * @param array $conf Optional associative array of additional + * configuration values. + * + * @return object The newly created concrete Log_observer + * instance, or null on an error. + */ + function &factory($type, $priority = PEAR_LOG_INFO, $conf = array()) + { + $type = strtolower($type); + $class = 'Log_observer_' . $type; + + /* + * If the desired class already exists (because the caller has supplied + * it from some custom location), simply instantiate and return a new + * instance. + */ + if (class_exists($class)) { + $object = &new $class($priority, $conf); + return $object; + } + + /* Support both the new-style and old-style file naming conventions. */ + $newstyle = true; + $classfile = dirname(__FILE__) . '/observer_' . $type . '.php'; + + if (!file_exists($classfile)) { + $classfile = 'Log/' . $type . '.php'; + $newstyle = false; + } + + /* + * Attempt to include our version of the named class, but don't treat + * a failure as fatal. The caller may have already included their own + * version of the named class. + */ + @include_once $classfile; + + /* If the class exists, return a new instance of it. */ + if (class_exists($class)) { + /* Support both new-style and old-style construction. */ + if ($newstyle) { + $object = &new $class($priority, $conf); + } else { + $object = &new $class($priority); + } + return $object; + } + + $null = null; + return $null; + } + + /** + * This is a stub method to make sure that Log_Observer classes do + * something when they are notified of a message. The default behavior + * is to just print the message, which is obviously not desireable in + * practically any situation - which is why you need to override this + * method. :) + * + * @param array $event A hash describing the log event. + */ + function notify($event) + { + print_r($event); + } +} diff --git a/include/PEAR/Log/sql.php b/include/PEAR/Log/sql.php new file mode 100644 index 0000000..661245d --- /dev/null +++ b/include/PEAR/Log/sql.php @@ -0,0 +1,294 @@ + + * @since Horde 1.3 + * @since Log 1.0 + * @package Log + * + * @example sql.php Using the SQL handler. + */ +class Log_sql extends Log +{ + /** + * Variable containing the DSN information. + * @var mixed + * @access private + */ + var $_dsn = ''; + + /** + * String containing the SQL insertion statement. + * + * @var string + * @access private + */ + var $_sql = ''; + + /** + * Array containing our set of DB configuration options. + * @var array + * @access private + */ + var $_options = array('persistent' => true); + + /** + * Object holding the database handle. + * @var object + * @access private + */ + var $_db = null; + + /** + * Resource holding the prepared statement handle. + * @var resource + * @access private + */ + var $_statement = null; + + /** + * Flag indicating that we're using an existing database connection. + * @var boolean + * @access private + */ + var $_existingConnection = false; + + /** + * String holding the database table to use. + * @var string + * @access private + */ + var $_table = 'log_table'; + + /** + * String holding the name of the ID sequence. + * @var string + * @access private + */ + var $_sequence = 'log_id'; + + /** + * Maximum length of the $ident string. This corresponds to the size of + * the 'ident' column in the SQL table. + * @var integer + * @access private + */ + var $_identLimit = 16; + + + /** + * Constructs a new sql logging object. + * + * @param string $name The target SQL table. + * @param string $ident The identification field. + * @param array $conf The connection configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_sql($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_table = $name; + $this->_mask = Log::UPTO($level); + + /* Now that we have a table name, assign our SQL statement. */ + if (!empty($conf['sql'])) { + $this->_sql = $conf['sql']; + } else { + $this->_sql = 'INSERT INTO ' . $this->_table . + ' (id, logtime, ident, priority, message)' . + ' VALUES(?, CURRENT_TIMESTAMP, ?, ?, ?)'; + } + + /* If an options array was provided, use it. */ + if (isset($conf['options']) && is_array($conf['options'])) { + $this->_options = $conf['options']; + } + + /* If a specific sequence name was provided, use it. */ + if (!empty($conf['sequence'])) { + $this->_sequence = $conf['sequence']; + } + + /* If a specific sequence name was provided, use it. */ + if (isset($conf['identLimit'])) { + $this->_identLimit = $conf['identLimit']; + } + + /* Now that the ident limit is confirmed, set the ident string. */ + $this->setIdent($ident); + + /* If an existing database connection was provided, use it. */ + if (isset($conf['db'])) { + $this->_db = &$conf['db']; + $this->_existingConnection = true; + $this->_opened = true; + } else { + $this->_dsn = $conf['dsn']; + } + } + + /** + * Opens a connection to the database, if it has not already + * been opened. This is implicitly called by log(), if necessary. + * + * @return boolean True on success, false on failure. + * @access public + */ + function open() + { + if (!$this->_opened) { + /* Use the DSN and options to create a database connection. */ + $this->_db = &DB::connect($this->_dsn, $this->_options); + if (DB::isError($this->_db)) { + return false; + } + + /* Create a prepared statement for repeated use in log(). */ + if (!$this->_prepareStatement()) { + return false; + } + + /* We now consider out connection open. */ + $this->_opened = true; + } + + return $this->_opened; + } + + /** + * Closes the connection to the database if it is still open and we were + * the ones that opened it. It is the caller's responsible to close an + * existing connection that was passed to us via $conf['db']. + * + * @return boolean True on success, false on failure. + * @access public + */ + function close() + { + if ($this->_opened && !$this->_existingConnection) { + $this->_opened = false; + $this->_db->freePrepared($this->_statement); + return $this->_db->disconnect(); + } + + return ($this->_opened === false); + } + + /** + * Sets this Log instance's identification string. Note that this + * SQL-specific implementation will limit the length of the $ident string + * to sixteen (16) characters. + * + * @param string $ident The new identification string. + * + * @access public + * @since Log 1.8.5 + */ + function setIdent($ident) + { + $this->_ident = substr($ident, 0, $this->_identLimit); + } + + /** + * Inserts $message to the currently open database. Calls open(), + * if necessary. Also passes the message along to any Log_observer + * instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* If we don't already have our statement object yet, create it. */ + if (!is_object($this->_statement) && !$this->_prepareStatement()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build our set of values for this log entry. */ + $id = $this->_db->nextId($this->_sequence); + $values = array($id, $this->_ident, $priority, $message); + + /* Execute the SQL query for this log entry insertion. */ + $result =& $this->_db->execute($this->_statement, $values); + if (DB::isError($result)) { + return false; + } + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + + /** + * Prepare the SQL insertion statement. + * + * @return boolean True if the statement was successfully created. + * + * @access private + * @since Log 1.9.1 + */ + function _prepareStatement() + { + $this->_statement = $this->_db->prepare($this->_sql); + + /* Return success if we didn't generate an error. */ + return (DB::isError($this->_statement) === false); + } +} diff --git a/include/PEAR/Log/sqlite.php b/include/PEAR/Log/sqlite.php new file mode 100644 index 0000000..a558fee --- /dev/null +++ b/include/PEAR/Log/sqlite.php @@ -0,0 +1,225 @@ + + * @author Jon Parise + * @since Log 1.8.3 + * @package Log + * + * @example sqlite.php Using the Sqlite handler. + */ +class Log_sqlite extends Log +{ + /** + * Array containing the connection defaults + * @var array + * @access private + */ + var $_options = array('mode' => 0666, + 'persistent' => false); + + /** + * Object holding the database handle. + * @var object + * @access private + */ + var $_db = null; + + /** + * Flag indicating that we're using an existing database connection. + * @var boolean + * @access private + */ + var $_existingConnection = false; + + /** + * String holding the database table to use. + * @var string + * @access private + */ + var $_table = 'log_table'; + + + /** + * Constructs a new sql logging object. + * + * @param string $name The target SQL table. + * @param string $ident The identification field. + * @param mixed $conf Can be an array of configuration options used + * to open a new database connection + * or an already opened sqlite connection. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_sqlite($name, $ident = '', &$conf, $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_table = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (is_array($conf)) { + foreach ($conf as $k => $opt) { + $this->_options[$k] = $opt; + } + } else { + // If an existing database connection was provided, use it. + $this->_db =& $conf; + $this->_existingConnection = true; + } + } + + /** + * Opens a connection to the database, if it has not already + * been opened. This is implicitly called by log(), if necessary. + * + * @return boolean True on success, false on failure. + * @access public + */ + function open() + { + if (is_resource($this->_db)) { + $this->_opened = true; + return $this->_createTable(); + } else { + /* Set the connection function based on the 'persistent' option. */ + if (empty($this->_options['persistent'])) { + $connectFunction = 'sqlite_open'; + } else { + $connectFunction = 'sqlite_popen'; + } + + /* Attempt to connect to the database. */ + if ($this->_db = $connectFunction($this->_options['filename'], + (int)$this->_options['mode'], + $error)) { + $this->_opened = true; + return $this->_createTable(); + } + } + + return $this->_opened; + } + + /** + * Closes the connection to the database if it is still open and we were + * the ones that opened it. It is the caller's responsible to close an + * existing connection that was passed to us via $conf['db']. + * + * @return boolean True on success, false on failure. + * @access public + */ + function close() + { + /* We never close existing connections. */ + if ($this->_existingConnection) { + return false; + } + + if ($this->_opened) { + $this->_opened = false; + sqlite_close($this->_db); + } + + return ($this->_opened === false); + } + + /** + * Inserts $message to the currently open database. Calls open(), + * if necessary. Also passes the message along to any Log_observer + * instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + // Extract the string representation of the message. + $message = $this->_extractMessage($message); + + // Build the SQL query for this log entry insertion. + $q = sprintf('INSERT INTO [%s] (logtime, ident, priority, message) ' . + "VALUES ('%s', '%s', %d, '%s')", + $this->_table, + strftime('%Y-%m-%d %H:%M:%S', time()), + sqlite_escape_string($this->_ident), + $priority, + sqlite_escape_string($message)); + if (!($res = @sqlite_unbuffered_query($this->_db, $q))) { + return false; + } + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + + /** + * Checks whether the log table exists and creates it if necessary. + * + * @return boolean True on success or false on failure. + * @access private + */ + function _createTable() + { + $q = "SELECT name FROM sqlite_master WHERE name='" . $this->_table . + "' AND type='table'"; + + $res = sqlite_query($this->_db, $q); + + if (sqlite_num_rows($res) == 0) { + $q = 'CREATE TABLE [' . $this->_table . '] (' . + 'id INTEGER PRIMARY KEY NOT NULL, ' . + 'logtime NOT NULL, ' . + 'ident CHAR(16) NOT NULL, ' . + 'priority INT NOT NULL, ' . + 'message)'; + + if (!($res = sqlite_unbuffered_query($this->_db, $q))) { + return false; + } + } + + return true; + } + +} diff --git a/include/PEAR/Log/syslog.php b/include/PEAR/Log/syslog.php new file mode 100644 index 0000000..22af4b6 --- /dev/null +++ b/include/PEAR/Log/syslog.php @@ -0,0 +1,179 @@ + + * @author Jon Parise + * @since Horde 1.3 + * @since Log 1.0 + * @package Log + * + * @example syslog.php Using the syslog handler. + */ +class Log_syslog extends Log +{ + /** + * Integer holding the log facility to use. + * @var integer + * @access private + */ + var $_name = LOG_SYSLOG; + + /** + * Should we inherit the current syslog connection for this process, or + * should we call openlog() to start a new syslog connection? + * @var boolean + * @access private + */ + var $_inherit = false; + + /** + * Constructs a new syslog object. + * + * @param string $name The syslog facility. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_syslog($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + /* Ensure we have a valid integer value for $name. */ + if (empty($name) || !is_int($name)) { + $name = LOG_SYSLOG; + } + + if (isset($conf['inherit'])) { + $this->_inherit = $conf['inherit']; + $this->_opened = $this->_inherit; + } + + $this->_id = md5(microtime()); + $this->_name = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + } + + /** + * Opens a connection to the system logger, if it has not already + * been opened. This is implicitly called by log(), if necessary. + * @access public + */ + function open() + { + if (!$this->_opened) { + $this->_opened = openlog($this->_ident, LOG_PID, $this->_name); + } + + return $this->_opened; + } + + /** + * Closes the connection to the system logger, if it is open. + * @access public + */ + function close() + { + if ($this->_opened && !$this->_inherit) { + closelog(); + $this->_opened = false; + } + + return true; + } + + /** + * Sends $message to the currently open syslog connection. Calls + * open() if necessary. Also passes the message along to any Log_observer + * instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param int $priority (optional) The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build a syslog priority value based on our current configuration. */ + $priority = $this->_toSyslog($priority); + if ($this->_inherit) { + $priority |= $this->_name; + } + + if (!syslog($priority, $message)) { + return false; + } + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + + /** + * Converts a PEAR_LOG_* constant into a syslog LOG_* constant. + * + * This function exists because, under Windows, not all of the LOG_* + * constants have unique values. Instead, the PEAR_LOG_* were introduced + * for global use, with the conversion to the LOG_* constants kept local to + * to the syslog driver. + * + * @param int $priority PEAR_LOG_* value to convert to LOG_* value. + * + * @return The LOG_* representation of $priority. + * + * @access private + */ + function _toSyslog($priority) + { + static $priorities = array( + PEAR_LOG_EMERG => LOG_EMERG, + PEAR_LOG_ALERT => LOG_ALERT, + PEAR_LOG_CRIT => LOG_CRIT, + PEAR_LOG_ERR => LOG_ERR, + PEAR_LOG_WARNING => LOG_WARNING, + PEAR_LOG_NOTICE => LOG_NOTICE, + PEAR_LOG_INFO => LOG_INFO, + PEAR_LOG_DEBUG => LOG_DEBUG + ); + + /* If we're passed an unknown priority, default to LOG_INFO. */ + if (!is_int($priority) || !in_array($priority, $priorities)) { + return LOG_INFO; + } + + return $priorities[$priority]; + } + +} diff --git a/include/PEAR/Log/win.php b/include/PEAR/Log/win.php new file mode 100644 index 0000000..21795d9 --- /dev/null +++ b/include/PEAR/Log/win.php @@ -0,0 +1,269 @@ + + * @since Log 1.7.0 + * @package Log + * + * @example win.php Using the window handler. + */ +class Log_win extends Log +{ + /** + * The name of the output window. + * @var string + * @access private + */ + var $_name = 'LogWindow'; + + /** + * The title of the output window. + * @var string + * @access private + */ + var $_title = 'Log Output Window'; + + /** + * Mapping of log priorities to styles. + * @var array + * @access private + */ + var $_styles = array( + PEAR_LOG_EMERG => 'color: red;', + PEAR_LOG_ALERT => 'color: orange;', + PEAR_LOG_CRIT => 'color: yellow;', + PEAR_LOG_ERR => 'color: green;', + PEAR_LOG_WARNING => 'color: blue;', + PEAR_LOG_NOTICE => 'color: indigo;', + PEAR_LOG_INFO => 'color: violet;', + PEAR_LOG_DEBUG => 'color: black;' + ); + + /** + * String buffer that holds line that are pending output. + * @var array + * @access private + */ + var $_buffer = array(); + + /** + * Constructs a new Log_win object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_win($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_name = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (isset($conf['title'])) { + $this->_title = $conf['title']; + } + if (isset($conf['styles']) && is_array($conf['styles'])) { + $this->_styles = $conf['styles']; + } + if (isset($conf['colors']) && is_array($conf['colors'])) { + foreach ($conf['colors'] as $level => $color) { + $this->_styles[$level] .= "color: $color;"; + } + } + + register_shutdown_function(array(&$this, '_Log_win')); + } + + /** + * Destructor + */ + function _Log_win() + { + if ($this->_opened || (count($this->_buffer) > 0)) { + $this->close(); + } + } + + /** + * The first time open() is called, it will open a new browser window and + * prepare it for output. + * + * This is implicitly called by log(), if necessary. + * + * @access public + */ + function open() + { + if (!$this->_opened) { + $win = $this->_name; + $styles = $this->_styles; + + if (!empty($this->_ident)) { + $identHeader = "$win.document.writeln('Ident')"; + } else { + $identHeader = ''; + } + + echo <<< EOT + +EOT; + $this->_opened = true; + } + + return $this->_opened; + } + + /** + * Closes the output stream if it is open. If there are still pending + * lines in the output buffer, the output window will be opened so that + * the buffer can be drained. + * + * @access public + */ + function close() + { + /* + * If there are still lines waiting to be written, open the output + * window so that we can drain the buffer. + */ + if (!$this->_opened && (count($this->_buffer) > 0)) { + $this->open(); + } + + if ($this->_opened) { + $this->_writeln(''); + $this->_writeln(''); + $this->_opened = false; + } + + return ($this->_opened === false); + } + + /** + * Writes a single line of text to the output window. + * + * @param string $line The line of text to write. + * + * @access private + */ + function _writeln($line) + { + /* Add this line to our output buffer. */ + $this->_buffer[] = $line; + + /* Buffer the output until this page's headers have been sent. */ + if (!headers_sent()) { + return; + } + + /* If we haven't already opened the output window, do so now. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Drain the buffer to the output window. */ + $win = $this->_name; + foreach ($this->_buffer as $line) { + echo "\n"; + } + + /* Now that the buffer has been drained, clear it. */ + $this->_buffer = array(); + } + + /** + * Logs $message to the output window. The message is also passed along + * to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + $message = preg_replace('/\r\n|\n|\r/', '
', $message); + + list($usec, $sec) = explode(' ', microtime()); + + /* Build the output line that contains the log entry row. */ + $line = ''; + $line .= sprintf('%s.%s', + strftime('%H:%M:%S', $sec), substr($usec, 2, 2)); + if (!empty($this->_ident)) { + $line .= '' . $this->_ident . ''; + } + $line .= '' . ucfirst($this->priorityToString($priority)) . ''; + $line .= sprintf('%s', $priority, $message); + $line .= ''; + + $this->_writeln($line); + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/include/PEAR/Net/Socket.php b/include/PEAR/Net/Socket.php new file mode 100644 index 0000000..96f2c4b --- /dev/null +++ b/include/PEAR/Net/Socket.php @@ -0,0 +1,528 @@ + | +// | Chuck Hagenbuch | +// +----------------------------------------------------------------------+ +// +// $Id: Socket.php 32 2005-08-01 06:21:02Z dancoulter $ + +require_once 'PEAR.php'; + +define('NET_SOCKET_READ', 1); +define('NET_SOCKET_WRITE', 2); +define('NET_SOCKET_ERROR', 3); + +/** + * Generalized Socket class. + * + * @version 1.1 + * @author Stig Bakken + * @author Chuck Hagenbuch + */ +class Net_Socket extends PEAR { + + /** + * Socket file pointer. + * @var resource $fp + */ + var $fp = null; + + /** + * Whether the socket is blocking. Defaults to true. + * @var boolean $blocking + */ + var $blocking = true; + + /** + * Whether the socket is persistent. Defaults to false. + * @var boolean $persistent + */ + var $persistent = false; + + /** + * The IP address to connect to. + * @var string $addr + */ + var $addr = ''; + + /** + * The port number to connect to. + * @var integer $port + */ + var $port = 0; + + /** + * Number of seconds to wait on socket connections before assuming + * there's no more data. Defaults to no timeout. + * @var integer $timeout + */ + var $timeout = false; + + /** + * Number of bytes to read at a time in readLine() and + * readAll(). Defaults to 2048. + * @var integer $lineLength + */ + var $lineLength = 2048; + + /** + * Connect to the specified port. If called when the socket is + * already connected, it disconnects and connects again. + * + * @param string $addr IP address or host name. + * @param integer $port TCP port number. + * @param boolean $persistent (optional) Whether the connection is + * persistent (kept open between requests + * by the web server). + * @param integer $timeout (optional) How long to wait for data. + * @param array $options See options for stream_context_create. + * + * @access public + * + * @return boolean | PEAR_Error True on success or a PEAR_Error on failure. + */ + function connect($addr, $port = 0, $persistent = null, $timeout = null, $options = null) + { + if (is_resource($this->fp)) { + @fclose($this->fp); + $this->fp = null; + } + + if (!$addr) { + return $this->raiseError('$addr cannot be empty'); + } elseif (strspn($addr, '.0123456789') == strlen($addr) || + strstr($addr, '/') !== false) { + $this->addr = $addr; + } else { + $this->addr = @gethostbyname($addr); + } + + $this->port = $port % 65536; + + if ($persistent !== null) { + $this->persistent = $persistent; + } + + if ($timeout !== null) { + $this->timeout = $timeout; + } + + $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen'; + $errno = 0; + $errstr = ''; + if ($options && function_exists('stream_context_create')) { + if ($this->timeout) { + $timeout = $this->timeout; + } else { + $timeout = 0; + } + $context = stream_context_create($options); + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout, $context); + } else { + if ($this->timeout) { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout); + } else { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr); + } + } + + if (!$fp) { + return $this->raiseError($errstr, $errno); + } + + $this->fp = $fp; + + return $this->setBlocking($this->blocking); + } + + /** + * Disconnects from the peer, closes the socket. + * + * @access public + * @return mixed true on success or an error object otherwise + */ + function disconnect() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + @fclose($this->fp); + $this->fp = null; + return true; + } + + /** + * Find out if the socket is in blocking mode. + * + * @access public + * @return boolean The current blocking mode. + */ + function isBlocking() + { + return $this->blocking; + } + + /** + * Sets whether the socket connection should be blocking or + * not. A read call to a non-blocking socket will return immediately + * if there is no data available, whereas it will block until there + * is data for blocking sockets. + * + * @param boolean $mode True for blocking sockets, false for nonblocking. + * @access public + * @return mixed true on success or an error object otherwise + */ + function setBlocking($mode) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $this->blocking = $mode; + socket_set_blocking($this->fp, $this->blocking); + return true; + } + + /** + * Sets the timeout value on socket descriptor, + * expressed in the sum of seconds and microseconds + * + * @param integer $seconds Seconds. + * @param integer $microseconds Microseconds. + * @access public + * @return mixed true on success or an error object otherwise + */ + function setTimeout($seconds, $microseconds) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return socket_set_timeout($this->fp, $seconds, $microseconds); + } + + /** + * Returns information about an existing socket resource. + * Currently returns four entries in the result array: + * + *

+ * timed_out (bool) - The socket timed out waiting for data
+ * blocked (bool) - The socket was blocked
+ * eof (bool) - Indicates EOF event
+ * unread_bytes (int) - Number of bytes left in the socket buffer
+ *

+ * + * @access public + * @return mixed Array containing information about existing socket resource or an error object otherwise + */ + function getStatus() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return socket_get_status($this->fp); + } + + /** + * Get a specified line of data + * + * @access public + * @return $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + function gets($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fgets($this->fp, $size); + } + + /** + * Read a specified amount of data. This is guaranteed to return, + * and has the added benefit of getting everything in one fread() + * chunk; if you know the size of the data you're getting + * beforehand, this is definitely the way to go. + * + * @param integer $size The number of bytes to read from the socket. + * @access public + * @return $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + function read($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fread($this->fp, $size); + } + + /** + * Write a specified amount of data. + * + * @param string $data Data to write. + * @param integer $blocksize Amount of data to write at once. + * NULL means all at once. + * + * @access public + * @return mixed true on success or an error object otherwise + */ + function write($data, $blocksize = null) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + if (is_null($blocksize) && !OS_WINDOWS) { + return fwrite($this->fp, $data); + } else { + if (is_null($blocksize)) { + $blocksize = 1024; + } + + $pos = 0; + $size = strlen($data); + while ($pos < $size) { + $written = @fwrite($this->fp, substr($data, $pos, $blocksize)); + if ($written === false) { + return false; + } + $pos += $written; + } + + return $pos; + } + } + + /** + * Write a line of data to the socket, followed by a trailing "\r\n". + * + * @access public + * @return mixed fputs result, or an error + */ + function writeLine($data) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return fwrite($this->fp, $data . "\r\n"); + } + + /** + * Tests for end-of-file on a socket descriptor. + * + * @access public + * @return bool + */ + function eof() + { + return (is_resource($this->fp) && feof($this->fp)); + } + + /** + * Reads a byte of data + * + * @access public + * @return 1 byte of data from the socket, or a PEAR_Error if + * not connected. + */ + function readByte() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return ord(@fread($this->fp, 1)); + } + + /** + * Reads a word of data + * + * @access public + * @return 1 word of data from the socket, or a PEAR_Error if + * not connected. + */ + function readWord() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 2); + return (ord($buf[0]) + (ord($buf[1]) << 8)); + } + + /** + * Reads an int of data + * + * @access public + * @return integer 1 int of data from the socket, or a PEAR_Error if + * not connected. + */ + function readInt() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 4); + return (ord($buf[0]) + (ord($buf[1]) << 8) + + (ord($buf[2]) << 16) + (ord($buf[3]) << 24)); + } + + /** + * Reads a zero-terminated string of data + * + * @access public + * @return string, or a PEAR_Error if + * not connected. + */ + function readString() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $string = ''; + while (($char = @fread($this->fp, 1)) != "\x00") { + $string .= $char; + } + return $string; + } + + /** + * Reads an IP Address and returns it in a dot formated string + * + * @access public + * @return Dot formated string, or a PEAR_Error if + * not connected. + */ + function readIPAddress() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 4); + return sprintf("%s.%s.%s.%s", ord($buf[0]), ord($buf[1]), + ord($buf[2]), ord($buf[3])); + } + + /** + * Read until either the end of the socket or a newline, whichever + * comes first. Strips the trailing newline from the returned data. + * + * @access public + * @return All available data up to a newline, without that + * newline, or until the end of the socket, or a PEAR_Error if + * not connected. + */ + function readLine() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $line = ''; + $timeout = time() + $this->timeout; + while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { + $line .= @fgets($this->fp, $this->lineLength); + if (substr($line, -1) == "\n") { + return rtrim($line, "\r\n"); + } + } + return $line; + } + + /** + * Read until the socket closes, or until there is no more data in + * the inner PHP buffer. If the inner buffer is empty, in blocking + * mode we wait for at least 1 byte of data. Therefore, in + * blocking mode, if there is no data at all to be read, this + * function will never exit (unless the socket is closed on the + * remote end). + * + * @access public + * + * @return string All data until the socket closes, or a PEAR_Error if + * not connected. + */ + function readAll() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $data = ''; + while (!feof($this->fp)) { + $data .= @fread($this->fp, $this->lineLength); + } + return $data; + } + + /** + * Runs the equivalent of the select() system call on the socket + * with a timeout specified by tv_sec and tv_usec. + * + * @param integer $state Which of read/write/error to check for. + * @param integer $tv_sec Number of seconds for timeout. + * @param integer $tv_usec Number of microseconds for timeout. + * + * @access public + * @return False if select fails, integer describing which of read/write/error + * are ready, or PEAR_Error if not connected. + */ + function select($state, $tv_sec, $tv_usec = 0) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $read = null; + $write = null; + $except = null; + if ($state & NET_SOCKET_READ) { + $read[] = $this->fp; + } + if ($state & NET_SOCKET_WRITE) { + $write[] = $this->fp; + } + if ($state & NET_SOCKET_ERROR) { + $except[] = $this->fp; + } + if (false === ($sr = stream_select($read, $write, $except, $tv_sec, $tv_usec))) { + return false; + } + + $result = 0; + if (count($read)) { + $result |= NET_SOCKET_READ; + } + if (count($write)) { + $result |= NET_SOCKET_WRITE; + } + if (count($except)) { + $result |= NET_SOCKET_ERROR; + } + return $result; + } + +} diff --git a/include/PEAR/Net/URL.php b/include/PEAR/Net/URL.php new file mode 100644 index 0000000..38e26fd --- /dev/null +++ b/include/PEAR/Net/URL.php @@ -0,0 +1,410 @@ + | +// +-----------------------------------------------------------------------+ +// +// $Id: URL.php 32 2005-08-01 06:21:02Z dancoulter $ +// +// Net_URL Class + +class Net_URL +{ + /** + * Full url + * @var string + */ + var $url; + + /** + * Protocol + * @var string + */ + var $protocol; + + /** + * Username + * @var string + */ + var $username; + + /** + * Password + * @var string + */ + var $password; + + /** + * Host + * @var string + */ + var $host; + + /** + * Port + * @var integer + */ + var $port; + + /** + * Path + * @var string + */ + var $path; + + /** + * Query string + * @var array + */ + var $querystring; + + /** + * Anchor + * @var string + */ + var $anchor; + + /** + * Whether to use [] + * @var bool + */ + var $useBrackets; + + /** + * PHP4 Constructor + * + * @see __construct() + */ + function Net_URL($url = null, $useBrackets = true) + { + $this->__construct($url, $useBrackets); + } + + /** + * PHP5 Constructor + * + * Parses the given url and stores the various parts + * Defaults are used in certain cases + * + * @param string $url Optional URL + * @param bool $useBrackets Whether to use square brackets when + * multiple querystrings with the same name + * exist + */ + function __construct($url = null, $useBrackets = true) + { + $HTTP_SERVER_VARS = !empty($_SERVER) ? $_SERVER : $GLOBALS['HTTP_SERVER_VARS']; + + $this->useBrackets = $useBrackets; + $this->url = $url; + $this->user = ''; + $this->pass = ''; + $this->host = ''; + $this->port = 80; + $this->path = ''; + $this->querystring = array(); + $this->anchor = ''; + + // Only use defaults if not an absolute URL given + if (!preg_match('/^[a-z0-9]+:\/\//i', $url)) { + + $this->protocol = (@$HTTP_SERVER_VARS['HTTPS'] == 'on' ? 'https' : 'http'); + + /** + * Figure out host/port + */ + if (!empty($HTTP_SERVER_VARS['HTTP_HOST']) AND preg_match('/^(.*)(:([0-9]+))?$/U', $HTTP_SERVER_VARS['HTTP_HOST'], $matches)) { + $host = $matches[1]; + if (!empty($matches[3])) { + $port = $matches[3]; + } else { + $port = $this->getStandardPort($this->protocol); + } + } + + $this->user = ''; + $this->pass = ''; + $this->host = !empty($host) ? $host : (isset($HTTP_SERVER_VARS['SERVER_NAME']) ? $HTTP_SERVER_VARS['SERVER_NAME'] : 'localhost'); + $this->port = !empty($port) ? $port : (isset($HTTP_SERVER_VARS['SERVER_PORT']) ? $HTTP_SERVER_VARS['SERVER_PORT'] : $this->getStandardPort($this->protocol)); + $this->path = !empty($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : '/'; + $this->querystring = isset($HTTP_SERVER_VARS['QUERY_STRING']) ? $this->_parseRawQuerystring($HTTP_SERVER_VARS['QUERY_STRING']) : null; + $this->anchor = ''; + } + + // Parse the url and store the various parts + if (!empty($url)) { + $urlinfo = parse_url($url); + + // Default querystring + $this->querystring = array(); + + foreach ($urlinfo as $key => $value) { + switch ($key) { + case 'scheme': + $this->protocol = $value; + $this->port = $this->getStandardPort($value); + break; + + case 'user': + case 'pass': + case 'host': + case 'port': + $this->$key = $value; + break; + + case 'path': + if ($value{0} == '/') { + $this->path = $value; + } else { + $path = dirname($this->path) == DIRECTORY_SEPARATOR ? '' : dirname($this->path); + $this->path = sprintf('%s/%s', $path, $value); + } + break; + + case 'query': + $this->querystring = $this->_parseRawQueryString($value); + break; + + case 'fragment': + $this->anchor = $value; + break; + } + } + } + } + + /** + * Returns full url + * + * @return string Full url + * @access public + */ + function getURL() + { + $querystring = $this->getQueryString(); + + $this->url = $this->protocol . '://' + . $this->user . (!empty($this->pass) ? ':' : '') + . $this->pass . (!empty($this->user) ? '@' : '') + . $this->host . ($this->port == $this->getStandardPort($this->protocol) ? '' : ':' . $this->port) + . $this->path + . (!empty($querystring) ? '?' . $querystring : '') + . (!empty($this->anchor) ? '#' . $this->anchor : ''); + + return $this->url; + } + + /** + * Adds a querystring item + * + * @param string $name Name of item + * @param string $value Value of item + * @param bool $preencoded Whether value is urlencoded or not, default = not + * @access public + */ + function addQueryString($name, $value, $preencoded = false) + { + if ($preencoded) { + $this->querystring[$name] = $value; + } else { + $this->querystring[$name] = is_array($value) ? array_map('rawurlencode', $value): rawurlencode($value); + } + } + + /** + * Removes a querystring item + * + * @param string $name Name of item + * @access public + */ + function removeQueryString($name) + { + if (isset($this->querystring[$name])) { + unset($this->querystring[$name]); + } + } + + /** + * Sets the querystring to literally what you supply + * + * @param string $querystring The querystring data. Should be of the format foo=bar&x=y etc + * @access public + */ + function addRawQueryString($querystring) + { + $this->querystring = $this->_parseRawQueryString($querystring); + } + + /** + * Returns flat querystring + * + * @return string Querystring + * @access public + */ + function getQueryString() + { + if (!empty($this->querystring)) { + foreach ($this->querystring as $name => $value) { + if (is_array($value)) { + foreach ($value as $k => $v) { + $querystring[] = $this->useBrackets ? sprintf('%s[%s]=%s', $name, $k, $v) : ($name . '=' . $v); + } + } elseif (!is_null($value)) { + $querystring[] = $name . '=' . $value; + } else { + $querystring[] = $name; + } + } + $querystring = implode(ini_get('arg_separator.output'), $querystring); + } else { + $querystring = ''; + } + + return $querystring; + } + + /** + * Parses raw querystring and returns an array of it + * + * @param string $querystring The querystring to parse + * @return array An array of the querystring data + * @access private + */ + function _parseRawQuerystring($querystring) + { + $parts = preg_split('/[' . preg_quote(ini_get('arg_separator.input'), '/') . ']/', $querystring, -1, PREG_SPLIT_NO_EMPTY); + $return = array(); + + foreach ($parts as $part) { + if (strpos($part, '=') !== false) { + $value = substr($part, strpos($part, '=') + 1); + $key = substr($part, 0, strpos($part, '=')); + } else { + $value = null; + $key = $part; + } + if (substr($key, -2) == '[]') { + $key = substr($key, 0, -2); + if (@!is_array($return[$key])) { + $return[$key] = array(); + $return[$key][] = $value; + } else { + $return[$key][] = $value; + } + } elseif (!$this->useBrackets AND !empty($return[$key])) { + $return[$key] = (array)$return[$key]; + $return[$key][] = $value; + } else { + $return[$key] = $value; + } + } + + return $return; + } + + /** + * Resolves //, ../ and ./ from a path and returns + * the result. Eg: + * + * /foo/bar/../boo.php => /foo/boo.php + * /foo/bar/../../boo.php => /boo.php + * /foo/bar/.././/boo.php => /foo/boo.php + * + * This method can also be called statically. + * + * @param string $url URL path to resolve + * @return string The result + */ + function resolvePath($path) + { + $path = explode('/', str_replace('//', '/', $path)); + + for ($i=0; $i 1 OR ($i == 1 AND $path[0] != '') ) ) { + unset($path[$i]); + unset($path[$i-1]); + $path = array_values($path); + $i -= 2; + + } elseif ($path[$i] == '..' AND $i == 1 AND $path[0] == '') { + unset($path[$i]); + $path = array_values($path); + $i--; + + } else { + continue; + } + } + + return implode('/', $path); + } + + /** + * Returns the standard port number for a protocol + * + * @param string $scheme The protocol to lookup + * @return integer Port number or NULL if no scheme matches + * + * @author Philippe Jausions + */ + function getStandardPort($scheme) + { + switch (strtolower($scheme)) { + case 'http': return 80; + case 'https': return 443; + case 'ftp': return 21; + case 'imap': return 143; + case 'imaps': return 993; + case 'pop3': return 110; + case 'pop3s': return 995; + default: return null; + } + } + + /** + * Forces the URL to a particular protocol + * + * @param string $protocol Protocol to force the URL to + * @param integer $port Optional port (standard port is used by default) + */ + function setProtocol($protocol, $port = null) + { + $this->protocol = $protocol; + $this->port = is_null($port) ? $this->getStandardPort() : $port; + } + +} +?> diff --git a/include/PEAR/PEAR.php b/include/PEAR/PEAR.php new file mode 100644 index 0000000..8664aa0 --- /dev/null +++ b/include/PEAR/PEAR.php @@ -0,0 +1,1086 @@ + + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: PEAR.php 32 2005-08-01 06:21:02Z dancoulter $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +// instant backwards compatibility +if (!defined('PATH_SEPARATOR')) { + if (OS_WINDOWS) { + define('PATH_SEPARATOR', ';'); + } else { + define('PATH_SEPARATOR', ':'); + } +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Greg Beaver + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.4.0a12 + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + // {{{ properties + + /** + * Whether to enable internal debug messages. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + // }}} + + // {{{ constructor + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + if ($error_class !== null) { + $this->_error_class = $error_class; + } + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + // }}} + // {{{ destructor + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + // }}} + // {{{ getStaticProperty() + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + function &getStaticProperty($class, $var) + { + static $properties; + return $properties[$class][$var]; + } + + // }}} + // {{{ registerShutdownFunc() + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + function registerShutdownFunc($func, $args = array()) + { + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + // }}} + // {{{ isError() + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @access public + * @return bool true if parameter is an error + */ + function isError($data, $code = null) + { + if (is_a($data, 'PEAR_Error')) { + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } else { + return $data->getCode() == $code; + } + } + return false; + } + + // }}} + // {{{ setErrorHandling() + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + + function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + // }}} + // {{{ expectError() + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return sizeof($this->_expected_errors); + } + + // }}} + // {{{ popExpect() + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + // }}} + // {{{ _checkDelExpect() + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + + foreach ($this->_expected_errors AS $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + return $deleted; + } + + // }}} + // {{{ delExpect() + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + function delExpect($error_code) + { + $deleted = false; + + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; + // we walk through it trying to unset all + // values + foreach($error_code as $key => $error) { + if ($this->_checkDelExpect($error)) { + $deleted = true; + } else { + $deleted = false; + } + } + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } else { + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + } else { + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + } + + // }}} + // {{{ raiseError() + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @access public + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + function raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp))) { + $mode = PEAR_ERROR_RETURN; + } + } + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + if ($skipmsg) { + return new $ec($code, $mode, $options, $userinfo); + } else { + return new $ec($message, $code, $mode, $options, $userinfo); + } + } + + // }}} + // {{{ throwError() + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param string $message + * + */ + function throwError($message = null, + $code = null, + $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + return $this->raiseError($message, $code, null, null, $userinfo); + } else { + return PEAR::raiseError($message, $code, null, null, $userinfo); + } + } + + // }}} + function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + // {{{ pushErrorHandling() + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + // }}} + // {{{ popErrorHandling() + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + // }}} + // {{{ loadExtension() + + /** + * OS independant PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + function loadExtension($ext) + { + if (!extension_loaded($ext)) { + // if either returns true dl() will produce a FATAL error, stop that + if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { + return false; + } + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } + return true; + } + + // }}} +} + +// {{{ _PEAR_call_destructors() + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +// }}} +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Gregory Beaver + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.4.0a12 + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + // {{{ properties + + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + // }}} + // {{{ constructor + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + if (function_exists("debug_backtrace") && !defined('PEAR_IGNORE_BACKTRACE')) { + $this->backtrace = debug_backtrace(); + } + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + $this->level = $options; + $this->callback = null; + } + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + printf($format, $this->getMessage()); + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + die(sprintf($format, $msg)); + } + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + } + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_ErrorStack for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + // }}} + // {{{ getMode() + + /** + * Get the error mode from an error object. + * + * @return int error mode + * @access public + */ + function getMode() { + return $this->mode; + } + + // }}} + // {{{ getCallback() + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + * @access public + */ + function getCallback() { + return $this->callback; + } + + // }}} + // {{{ getMessage() + + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + + // }}} + // {{{ getCode() + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + // }}} + // {{{ getType() + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + // }}} + // {{{ getUserInfo() + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + // }}} + // {{{ getDebugInfo() + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + // }}} + // {{{ getBacktrace() + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + // }}} + // {{{ addUserInfo() + + function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + // }}} + // {{{ toString() + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } + + // }}} +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/include/phpFlickr-2.1.0/ChangeLog.txt b/include/phpFlickr-2.1.0/ChangeLog.txt new file mode 100644 index 0000000..0c39d76 --- /dev/null +++ b/include/phpFlickr-2.1.0/ChangeLog.txt @@ -0,0 +1,269 @@ +--------------------- +phpFlickr - Changelog +--------------------- + +Version 2.1.0 +------------- + +2006-11-21 Dan Coulter + * phpFlickr.php: Updated the buildPhotoURL method to + reflect recent changes in the Flickr API. Also added the + photos_getFavorites method and added pagination arguments + to photosets_getPhotos.. + +2006-11-21 Dan Coulter + * phpFlickr.php: Added the activity methods. + +2006-10-18 Dan Coulter + * phpFlickr.php: Added the tags_getHotList method. + + +Version 2.0.0 +------------- + +2006-09-29 Dan Coulter + IMPORTANT: This version will not be 100% backwards compatible. + It may very well break older apps if you try to copy it over + an older version of phpFlickr. Be sure you test thoroughly. + + * phpFlickr.php: Updated class to use Flickr's Serialized PHP + format. Also, I revamped the error handling and caching. + Fixed a bug in file uploading. + * xml.php: Deleted. + * xml_SAXY_parser.php: Deleted. + + +Version 1.6.1 +------------- + +2006-09-01 Dan Coulter + * phpFlickr.php: Changed the license to the LGPL and + implemented my friendly geodata service. + + +Version 1.6 +----------- + +2006-08-29 Dan Coulter + * phpFlickr.php: Implemented the new comment and geocoding + API methods. + +2006-08-04 Dan Coulter + * phpFlickr.php: added support for new API methods. Also, + added useSAXY method to switch to the SAXY parser. + * xml.php: Changed parser so that it uses PHP's XML functions + by default, but you can choose to use SAXY if you may run + into unusual characters. Using SAXY requires alot more + memory for your scripts. + + +Version 1.6-beta +---------------- + +2006-06-19 Dan Coulter + * xml.php: Replaced PHP's XML functions with the SAXY_Parser + class to remove the problems with unusual characters that + PHP's functions created. + +2006-06-15 Dan Coulter + * phpFlickr.php: Added support for the new photo replacement API. + +Version 1.5.2 +------------- + +2006-05-07 Dan Coulter + * phpFlickr.php: Added privacy_filter parameter to + photosets_getPhotos(). + + +2006-03-13 Dan Coulter + * phpFlickr.php: Fixed bugs in contacts_getList and + contacts_getPublicList when they are returned with one + result. + +Version 1.5.1 +------------- + +2006-03-13 Dan Coulter + * phpFlickr.php: Added support for flickr.photos.comments.getList + +2006-03-11 Dan Coulter + * phpFlickr.php: Fixed a bug in photos_licenses_setLicense() + +Version 1.5 +----------- + +2006-02-15 Dan Coulter + * phpFlickr.php: Implemented support for 23's compatible API. + Implemented flickr.groups.search method. Fixed bug in + filesystem caching for PHP versions older than 4.3.0. + Stopped caching from changing the working directory. + Included support for HTTP proxy servers. + + +Version 1.4.3 +------------- + +2006-01-05 Dan Coulter + * phpFlickr.php: Added interestingness_getList() method. + Fixed a bug in favorites_getPublicList() that made it + return less data than it should have. Note: This + means that you need to adjust any calls to this function. + * xml.php: Added Escape character to replacements so that the + XML parser will not barf. + +2005-12-16 Dan Coulter + * phpFlickr.php: Fixed a bug in photosets_getPhotos() + definition. + + +Version 1.4.2 +------------- + +2005-12-13 Dan Coulter + * phpFlickr.php: Removed extra useless code from + favorites_remove(). + Added photo deletion method: photos_delete(). + Removed old authentication function login(). Flickr + has removed the old auth style from the API. The only + way to authenticate now is through the auth() function. + + +2005-12-12 Dan Coulter + * phpFlickr.php: Fixed a bug setting the PEAR include path + in Windows. + +2005-12-10 Dan Coulter + * phpFlickr.php: Fixed a bug that would cause database + caching to fail in Windows. + + +Version 1.4.1 +------------- + +2005-12-02 Dan Coulter + * phpFlickr.php: Added "user_id" parameter to the + groups_pools_getPhotos() method. Note, because of the + placement in the Flickr API documentation, this make break + some older applications. The third parameter must be + the user_id. + * phpFlickr.php: Changed buildPhotoURL() to use the new URL + scheme for photo files. + * xml.php: Fixed the bug that was causing an "undefined + index" notice. + +2005-11-15 Dan Coulter + * phpFlickr.php: fixed a session bug and added the "extras" + parameter to photosets_getPhotos. + +Version 1.4 +------------- + +2005-09-19 Dan Coulter + * phpFlickr.php: Fixed a bug in getCached() to remove an extra + database call that was being made. Avast! + +2005-09-18 Dan Coulter + * phpFlickr.php: Added support for uploading. Both synchronous + and asynchronous uploading have been implemented. + +Version 1.3.3 +------------- + +2005-09-18 Dan Coulter + * phpFlickr.php: Added support for flickr.photos.getAllContexts. + Adjusted behavior of certain methods to make results more consistant. + Added callMethod() function for generic access to methods. Made sure + that methods that set data do not cache the results. + +2005-09-14 Dan Coulter + * phpFlickr.php: Added support for flickr.photos.licenses.setLicense. + +2005-09-08 Dan Coulter + * phpFlickr.php: Changed variable definition from $error_num + to $error_code. + +2005-09-07 Dan Coulter + * phpFlickr.php: Fixed bug in photos_getNotInSet(). + +2005-08-23 Dan Coulter + * phpFlickr.php: Fixed file system detection. + +Version 1.3.2 +------------- + +2005-08-17 Dan Coulter + * phpFlickr.php: Fixed a bug in the include paths and a bug + that was causing the cache not to work. + +2005-08-16 Dan Coulter + * phpFlickr.php: Added filter parameter to contacts_getList(). + +2005-08-10 Dan Coulter + * phpFlickr.php: Added support for flickr.people.getUploadStatus + +2005-08-11 Dan Coulter + * xml.php: Fixed a bug that removed all newline characters from the + XML returned from Flickr. + +Version 1.3.1 +------------- + +2005-08-05 Dan Coulter + * auth.php: Updated to use Flickr's extra callback argument for the + redirect instead of a session variable. + +2005-08-04 Dan Coulter + * phpFlickr.php: Included code to add the packaged PEAR folder + in the include path. + + +Version 1.3 +------------- + +2005-07-22 Dan Coulter + * phpFlickr.php: Added support for the new authentication scheme. + Added the three new auth methods plus the phpFlickr::auth() function + to act as a simple authentication check. + Fixed bugs in the error handling functions. + * README.txt: Fixed several typos relating to photos_search() and to + the PEAR prereqs. + +Version 1.2.1 +------------- + +2005-06-30 Dan Coulter + * phpFlickr.php: Added a comment to the photos_search() method to + help people know how to use it. + * README.txt: Fixed several typos relating to photos_search() and to + the PEAR prereqs. + +2005-06-23 Dan Coulter + * phpFlickr.php: Fixed a bug in the buildPhotoURL method that caused + an invalid URL to be returned. + * phpFlickr.php: removed the login parameters from the constructor + and added "die_on_error" parameter. Added login() and two error + methods. + * README.txt: Edited text about constructor's parameters and included + information on new login method. + +Version 1.2 +----------- + +2005-06-12 Dan Coulter + * phpFlickr.php: Created functions for the new methods: photosets.addPhoto + and photosets.removePhoto. Also fixed some code formatting. + * phpFlickr.php: Changed URLs to reflect the new phpflickr.com site. + * README.txt: Changed URLs to reflect the new phpflickr.com site and added + a few notes about coding. + * example.php: Added an example php script. + +2005-06-11 Dan Coulter + * phpFlickr.php: Fixed a bug in the definition of groups_pools_getPhotos() + that made the fifth parameter not optional. + +Version 1.1 +----------- + +2005-06-09 Dan Coulter + * phpFlickr.php: Added caching functionality diff --git a/include/phpFlickr-2.1.0/README.txt b/include/phpFlickr-2.1.0/README.txt new file mode 100644 index 0000000..3046d27 --- /dev/null +++ b/include/phpFlickr-2.1.0/README.txt @@ -0,0 +1,250 @@ +phpFlickr Class 2.1.0 +Written by Dan Coulter (dancoulter@users.sourceforge.net) +Project Homepage: http://www.phpflickr.com/ +Sourceforge Project Page: http://www.sourceforge.net/projects/phpflickr/ +Released under GNU Lesser General Public License (http://www.gnu.org/copyleft/lgpl.html) +For more information about the class and upcoming tools and applications using it, +visit http://www.phpflickr.com/ or http://www.sourceforge.net/projects/phpflickr/ + +If you are interested in hiring me for a project (involving phpFlickr +or not), feel free to email me. + +Installation instructions: +1. Copy the files from the installation package into a folder on your + server. They need to be readible by your web server. You can put + them into an include folder defined in your php.ini file, if you + like, though it's not required. + +2. All you have to do now is include the file in your PHP scripts and + create an instance. For example: + $f = new phpFlickr(); + + The constructor has three arguments: + A. $api_key - This is the API key given to you by flickr.com. This + argument is required and you can get an API Key at: + http://www.flickr.com/services/api/key.gne + + B. $secret - The "secret" is optional because is not required to + make unauthenticated calls, but is absolutely required for the + new authentication API (see Authentication section below). You + will get one assigned alongside your api key. + + C. $die_on_error - This takes a boolean value and determines + whether the class will die (aka cease operation) if the API + returns an error statement. It defaults to false. Every method + will return false if the API returns an error. You can access + error messages using the getErrorCode() and getErrorMsg() + methods. + +3. All of the API methods have been implemented in my class. You can + see a full list and documentation here: + http://www.flickr.com/services/api/ + To call a method, remove the "flickr." part of the name and replace + any periods with underscores. For example, instead of + flickr.photos.search, you would call $f->photos_search() or instead + of flickr.photos.licenses.getInfo, you would call + $f->photos_licenses_getInfo() (yes, it is case sensitive). + + All functions have their arguments implemented in the list order on + their documentation page (a link to which is included with each + method in the phpFlickr clasS). The only exceptions to this are + photos_search(), photos_getWithoutGeodata() and + photos_getWithoutGeodata() which have so many optional arguments + that it's easier for everyone if you just have to pass an + associative array of arguments. See the comment in the + photos_search() definition in phpFlickr.php for more information. + + + +Authentication: + As of this release of the phpFlickr class there is only one authentication method + available to the API. This method is somewhat complex, but is far more secure and + allows your users to feel a little safer authenticating to your application. You'll + no longer have to ask for their username and password. + + Authentication API - http://www.flickr.com/services/api/auth.spec.html + + I know how complicated this API looks at first glance, so I've tried to + make this as transparent to the coding process. I'll go through the steps + you'll need to use this. Both the auth.php and getToken.php file will + need your API Key and Secret entered before you can use them. + + To have end users authenticate their accounts: + First, setup a callback script. I've included a callback script that + is pretty flexible. You'll find it in the package entitled "auth.php". + You'll need to go to flickr and point your api key to this file as the + callback script. Once you've done this, on any page that you want to + require the end user end user to authenticate their flickr account to + your app, just call the phpFlickr::auth() function with whatever + permission you need to use. + For example: + $f->auth("write"); + The three permissions are "read", "write" and "delete". The function + defaults to "read", if you leave it blank. + + Calling this function will send the user's browser to Flickr's page to + authenticate to your app. Once they have logged in, it will bounce + them back to your callback script which will redirect back to the + original page that you called the auth() function from after setting + a session variable to save their authentication token. If that session + variable exists, calling the auth() function will return the permissions + that the user granted your app on the Flickr page instead of redirecting + to an external page. + + To authenticate the app to your account to show your private pictures (for example) + This method will allow you to have the app authenticate to one specific + account, no matter who views your website. This is useful to display + private photos or photosets (among other things). + + *Note*: The method below is a little hard to understand, so I've setup a tool + to help you through this: http://www.phpflickr.com/tools/auth/. + + First, you'll have to setup a callback script with Flickr. Once you've + done that, edit line 12 of the included getToken.php file to reflect + which permissions you'll need for the app. Then browse to the page. + Once you've authorized the app with Flickr, it'll send you back to that + page which will give you a token which will look something like this: + 1234-567890abcdef1234 + Go to the file where you are creating an instance of phpFlickr (I suggest + an include file) and after you've created it set the token to use: + $f->setToken(""); + This token never expires, so you don't have to worry about having to + login periodically. + + +Using Caching: + Caching can be very important to a project. Just a few calls to the Flickr API + can take long enough to bore your average web user (depending on the calls you + are making). I've built in caching that will access either a database or files + in your filesystem. To enable caching, use the phpFlickr::enableCache() function. + This function requires at least two arguments. The first will be the type of + cache you're using (either "db" or "fs") + + 1. If you're using database caching, you'll need to supply a PEAR::DB connection + string. For example: + $flickr->enableCache("db", "mysql://user:password@server/database"); + The third (optional) argument is expiration of the cache in seconds (defaults + to 600). The fourth (optional) argument is the table where you want to store + the cache. This defaults to flickr_cache and will attempt to create the table + if it does not already exist. + + 2. If you're using filesystem caching, you'll need to supply a folder where the + web server has write access. For example: + $flickr->enableCache("fs", "/var/www/phpFlickrCache"); + The third (optional) argument is, the same as in the Database caching, an + expiration in seconds for the cache. + Note: filesystem caching will probably be slower than database caching. I + haven't done any tests of this, but if you get large amounts of calls, the + process of cleaning out old calls may get hard on your server. + + You may not want to allow the world to view the files that are created during + caching. If you want to hide this information, either make sure that your + permissions are set correctly, or disable the webserver from displaying + *.cache files. In Apache, you can specify this in the configuration files + or in a .htaccess file with the following directives: + + + Deny from all + + + Alternatively, you can specify a directory that is outside of the web server's + document root. + +Uploading + Uploading is pretty simple. Aside from being authenticated (see Authentication + section) the very minimum that you'll have to pass is a path to an image file on + your php server. You can do either synchronous or asynchronous uploading as follows: + synchronous: sync_upload("photo.jpg"); + asynchronous: async_upload("photo.jpg"); + + The basic difference is that synchronous uploading waits around until Flickr + processes the photo and returns a PhotoID. Asynchronous just uploads the + picture and gets a "ticketid" that you can use to check on the status of your + upload. Asynchronous is much faster, though the photoid won't be instantly + available for you. You can read more about asynchronous uploading here: + http://www.flickr.com/services/api/upload.async.html + + Both of the functions take the same arguments which are: + Photo: The path of the file to upload. + Title: The title of the photo. + Description: A description of the photo. May contain some limited HTML. + Tags: A space-separated list of tags to apply to the photo. + is_public: Set to 0 for no, 1 for yes. + is_friend: Set to 0 for no, 1 for yes. + is_family: Set to 0 for no, 1 for yes. + +Replacing Photos + Flickr has released API support for uploading a replacement photo. To use this + new method, just use the "replace" function in phpFlickr. You'll be required + to pass the file name and Flickr's photo ID. You need to authenticate your script + with "write" permissions before you can replace a photo. The arguments are: + Photo: The path of the file to upload. + Photo ID: The numeric Flickr ID of the photo you want to replace. + Async (optional): Set to 0 for a synchronous call, 1 for asynchronous. + If you use the asynchronous call, it will return a ticketid instead + of photoid. + +Other Notes: + 1. Many of the methods have optional arguments. For these, I have implemented + them in the same order that the Flickr API documentation lists them. PHP + allows for optional arguments in function calls, but if you want to use the + third optional argument, you have to fill in the others to the left first. + You can use the "NULL" value (without quotes) in the place of an actual + argument. For example: + $f->groups_pools_getPhotos($group_id, NULL, NULL, 10); + This will get the first ten photos from a specific group's pool. If you look + at the documentation, you will see that there is another argument, "page". I've + left it off because it appears after "per_page". + 2. Some people will need to ues phpFlickr from behind a proxy server. I've + implemented a method that will allow you to use an HTTP proxy for all of your + traffic. Let's say that you have a proxy server on your local server running + at port 8181. This is the code you would use: + $f = new phpFlickr("[api key]"); + $f->setProxy("localhost", "8181"); + After that, all of your calls will be automatically made through your proxy server. + 3. NOTE: phpFlickr's support of 23's API is only supported in 1.x + versions of phpFlickr. This will not work in 2.x until they + begin using serialized PHP. Don't even try! + As of phpFlickr version 1.5, I've decided to add support for 23's API. The + reasoning behind this is that 23 (a European-based photo sharing community) + has released an API based on Flickr's. Their main reason for doing so seems + to be compatibility for developers. This means that your app may instantly + start working for their users as well. All you'll need to do to get phpFlickr + to work with 23 is to use the following code: + $f = new phpFlickr("[api key]"); //With 23's API, you can make up your own API + //key or use one assigned to you from Flickr + $f->setService("23"); + To change back to flickr (the default setting): + $f->setService("flickr"); + I'm including this support with a few caveats: + -Currently, I'm only willing to support 23 insofar as they duplicate the + functionality of Flickr. If they begin to stray, create new methods, or + implement their own authentication scheme, I may end support of their + API at my discretion. + -There are some differences between the services. Their "square" thumbnail + format is larger than Flickr's. Also the photo page URLs have a different + format than Flickr's. I haven't used 23 much myself, so there may be + other important differences that I don't know about. + -Not all of Flickr's API methods have been implemented by 23. As far as I + can tell, they have setup dummy methods that will return empty result sets. + -According to their documentation, 23 does not yet support the "web application" + style authentication that Flickr does. This means that your app will not + authenticate to 23 as it would to Flickr. + More information about 23 and their API: + Homepage: http://www.23hq.com/ + About 23: http://www.23hq.com/23/about + Switching from Flickr's API: http://www.23hq.com/doc/api/switch + API Documentation: http://www.23hq.com/doc/api/index + Terms of Service: http://www.23hq.com/23/terms + + +That's it! Enjoy the class. Check out the project page (listed above) for updates +and news. I plan to implement file uploads and functions to aggregate data from +several different methods for easier use in a web application. Thanks for your +interest in this project! + + Please email me or submit all problems or questions to the Help topic on + the phpFlickr forums: + http://www.phpflickr.com/forums/ + + diff --git a/include/phpFlickr-2.1.0/auth.php b/include/phpFlickr-2.1.0/auth.php new file mode 100644 index 0000000..9582dc4 --- /dev/null +++ b/include/phpFlickr-2.1.0/auth.php @@ -0,0 +1,37 @@ +auth($permissions, false); + } else { + $f->auth_getToken($_GET['frob']); + } + + if (empty($redirect)) { + header("Location: " . $default_redirect); + } else { + header("Location: " . $redirect); + } + +?> \ No newline at end of file diff --git a/include/phpFlickr-2.1.0/example.php b/include/phpFlickr-2.1.0/example.php new file mode 100644 index 0000000..42796c4 --- /dev/null +++ b/include/phpFlickr-2.1.0/example.php @@ -0,0 +1,28 @@ +photos_getRecent(NULL, 1, 1); + +foreach ($recent['photo'] as $photo) { + $owner = $f->people_getInfo($photo['owner']); + echo ""; + echo $photo['title']; + echo " Owner: "; + echo ""; + echo $owner['username']; + echo "
"; +} +?> diff --git a/include/phpFlickr-2.1.0/getToken.php b/include/phpFlickr-2.1.0/getToken.php new file mode 100644 index 0000000..1d232e4 --- /dev/null +++ b/include/phpFlickr-2.1.0/getToken.php @@ -0,0 +1,19 @@ +", ""); + + //change this to the permissions you will need + $f->auth("read"); + + echo "Copy this token into your code: " . $_SESSION['phpFlickr_auth_token']; + +?> \ No newline at end of file diff --git a/include/phpFlickr-2.1.0/phpFlickr.php b/include/phpFlickr-2.1.0/phpFlickr.php new file mode 100644 index 0000000..2f615cb --- /dev/null +++ b/include/phpFlickr-2.1.0/phpFlickr.php @@ -0,0 +1,1363 @@ +api_key = $api_key; + $this->secret = $secret; + $this->die_on_error = $die_on_error; + $this->service = "flickr"; + + //Find the PHP version and store it for future reference + $this->php_version = explode("-", phpversion()); + $this->php_version = explode(".", $this->php_version[0]); + + //All calls to the API are done via the POST method using the PEAR::HTTP_Request package. + require_once 'HTTP/Request.php'; + $this->req =& new HTTP_Request(); + $this->req->setMethod(HTTP_REQUEST_METHOD_POST); + } + + function enableCache($type, $connection, $cache_expire = 600, $table = 'flickr_cache') + { + // Turns on caching. $type must be either "db" (for database caching) or "fs" (for filesystem). + // When using db, $connection must be a PEAR::DB connection string. Example: + // "mysql://user:password@server/database" + // If the $table, doesn't exist, it will attempt to create it. + // When using file system, caching, the $connection is the folder that the web server has write + // access to. Use absolute paths for best results. Relative paths may have unexpected behavior + // when you include this. They'll usually work, you'll just want to test them. + if ($type == 'db') { + require_once 'DB.php'; + $db =& DB::connect($connection); + if (PEAR::isError($db)) { + die($db->getMessage()); + } + + /* + * If high performance is crucial, you can easily comment + * out this query once you've created your database table. + */ + + $db->query(" + CREATE TABLE IF NOT EXISTS `$table` ( + `request` CHAR( 35 ) NOT NULL , + `response` MEDIUMTEXT NOT NULL , + `expiration` DATETIME NOT NULL , + INDEX ( `request` ) + ) TYPE = MYISAM"); + + if ($db->getOne("SELECT COUNT(*) FROM $table") > $this->max_cache_rows) { + $db->query("DELETE FROM $table WHERE expiration < DATE_SUB(NOW(), INTERVAL $cache_expire second)"); + $db->query('OPTIMIZE TABLE ' . $this->cache_table); + } + + $this->cache = 'db'; + $this->cache_db = $db; + $this->cache_table = $table; + } elseif ($type == 'fs') { + $this->cache = 'fs'; + $connection = realpath($connection); + $this->cache_dir = $connection; + if ($dir = opendir($this->cache_dir)) { + while ($file = readdir($dir)) { + if (substr($file, -6) == '.cache' && ((filemtime($this->cache_dir . '/' . $file) + $cache_expire) < time()) ) { + unlink($this->cache_dir . '/' . $file); + } + } + } + } + $this->cache_expire = $cache_expire; + } + + function getCached ($request) + { + //Checks the database or filesystem for a cached result to the request. + //If there is no cache result, it returns a value of false. If it finds one, + //it returns the unparsed XML. + $reqhash = md5(serialize($request)); + if ($this->cache == 'db') { + $result = $this->cache_db->getOne("SELECT response FROM " . $this->cache_table . " WHERE request = ? AND DATE_SUB(NOW(), INTERVAL " . (int) $this->cache_expire . " SECOND) < expiration", $reqhash); + if (!empty($result)) { + return $result; + } + } elseif ($this->cache == 'fs') { + $file = $this->cache_dir . '/' . $reqhash . '.cache'; + if (file_exists($file)) { + if ($this->php_version[0] > 4 || ($this->php_version[0] == 4 && $this->php_version[1] >= 3)) { + return file_get_contents($file); + } else { + return implode('', file($file)); + } + } + } + return false; + } + + function cache ($request, $response) + { + //Caches the unparsed XML of a request. + $reqhash = md5(serialize($request)); + if ($this->cache == 'db') { + //$this->cache_db->query("DELETE FROM $this->cache_table WHERE request = '$reqhash'"); + if ($this->cache_db->getOne("SELECT COUNT(*) FROM {$this->cache_table} WHERE request = '$reqhash'")) { + $sql = "UPDATE " . $this->cache_table . " SET response = ?, expiration = ? WHERE request = ?"; + $this->cache_db->query($sql, array($response, strftime("%Y-%m-%d %H:%M:%S"), $reqhash)); + } else { + $sql = "INSERT INTO " . $this->cache_table . " (request, response, expiration) VALUES ('$reqhash', '" . str_replace("'", "''", $response) . "', '" . strftime("%Y-%m-%d %H:%M:%S") . "')"; + $this->cache_db->query($sql); + } + } elseif ($this->cache == "fs") { + $file = $this->cache_dir . "/" . $reqhash . ".cache"; + $fstream = fopen($file, "w"); + $result = fwrite($fstream,$response); + fclose($fstream); + return $result; + } + return false; + } + + function request ($command, $args = array(), $nocache = false) + { + //Sends a request to Flickr's REST endpoint via POST. + $this->req->setURL($this->REST); + $this->req->clearPostData(); + if (substr($command,0,7) != "flickr.") { + $command = "flickr." . $command; + } + + //Process arguments, including method and login data. + $args = array_merge(array("method" => $command, "format" => "php_serial", "api_key" => $this->api_key), $args); + if (!empty($this->token)) { + $args = array_merge($args, array("auth_token" => $this->token)); + } elseif (!empty($_SESSION['phpFlickr_auth_token'])) { + $args = array_merge($args, array("auth_token" => $_SESSION['phpFlickr_auth_token'])); + } + ksort($args); + $auth_sig = ""; + if (!($this->response = $this->getCached($args)) || $nocache) { + foreach ($args as $key => $data) { + $auth_sig .= $key . $data; + $this->req->addPostData($key, $data); + } + if (!empty($this->secret)) { + $api_sig = md5($this->secret . $auth_sig); + $this->req->addPostData("api_sig", $api_sig); + } + + //Send Requests + if ($this->req->sendRequest()) { + $this->response = $this->req->getResponseBody(); + $this->cache($args, $this->response); + } else { + die("There has been a problem sending your command to the server."); + } + } + /* + * Uncomment this line (and comment out the next one) if you're doing large queries + * and you're concerned about time. This will, however, change the structure of + * the result, so be sure that you look at the results. + */ + //$this->parsed_response = unserialize($this->response); + $this->parsed_response = $this->clean_text_nodes(unserialize($this->response)); + if ($this->parsed_response['stat'] == 'fail') { + if ($this->die_on_error) die("The Flickr API returned the following error: #{$this->parsed_response['code']} - {$this->parsed_response['message']}"); + else { + $this->error_code = $this->parsed_response['code']; + $this->error_msg = $this->parsed_response['message']; + $this->parsed_response = false; + } + } else { + $this->error_code = false; + $this->error_msg = false; + } + return $this->response; + } + + function clean_text_nodes($arr) { + if (!is_array($arr)) { + return $arr; + } elseif (count($arr) == 0) { + return $arr; + } elseif (count($arr) == 1 && array_key_exists('_content', $arr)) { + return $arr['_content']; + } else { + foreach ($arr as $key => $element) { + $arr[$key] = $this->clean_text_nodes($element); + } + return($arr); + } + } + + function setToken($token) + { + // Sets an authentication token to use instead of the session variable + $this->token = $token; + } + + function setProxy($server, $port) + { + // Sets the proxy for all phpFlickr calls. + $this->req->setProxy($server, $port); + } + + function getErrorCode() + { + // Returns the error code of the last call. If the last call did not + // return an error. This will return a false boolean. + return $this->error_code; + } + + function getErrorMsg() + { + // Returns the error message of the last call. If the last call did not + // return an error. This will return a false boolean. + return $this->error_msg; + } + + /* These functions are front ends for the flickr calls */ + function buildPhotoURL ($photo, $size = "Medium") + { + //receives an array (can use the individual photo data returned + //from an API call) and returns a URL (doesn't mean that the + //file size exists) + $sizes = array( + "square" => "_s", + "thumbnail" => "_t", + "small" => "_m", + "medium" => "", + "medium640" => "_z", + "large" => "_b", + "original" => "_o" + ); + + $size = strtolower($size); + if (!array_key_exists($size, $sizes)) { + $size = "medium"; + } + + if ($size == "original") { + $url = "https://farm" . $photo['farm'] . ".static.flickr.com/" . $photo['server'] . "/" . $photo['id'] . "_" . $photo['originalsecret'] . "_o" . "." . $photo['originalformat']; + } else { + $url = "https://farm" . $photo['farm'] . ".static.flickr.com/" . $photo['server'] . "/" . $photo['id'] . "_" . $photo['secret'] . $sizes[$size] . ".jpg"; + } + return $url; + } + + function getFriendlyGeodata($lat, $lon) { + /* I've added this method to get the friendly geodata (i.e. 'in New York, NY') that the + * website provides, but isn't available in the API. I'm providing this service as long + * as it doesn't flood my server with requests and crash it all the time. + */ + return unserialize(file_get_contents('https://phpflickr.com/geodata/?format=php&lat=' . $lat . '&lon=' . $lon)); + } + + function sync_upload ($photo, $title = null, $description = null, $tags = null, $is_public = null, $is_friend = null, $is_family = null) { + $upload_req =& new HTTP_Request(); + $upload_req->setMethod(HTTP_REQUEST_METHOD_POST); + + + $upload_req->setURL($this->Upload); + $upload_req->clearPostData(); + + //Process arguments, including method and login data. + $args = array("api_key" => $this->api_key, "title" => $title, "description" => $description, "tags" => $tags, "is_public" => $is_public, "is_friend" => $is_friend, "is_family" => $is_family); + if (!empty($this->email)) { + $args = array_merge($args, array("email" => $this->email)); + } + if (!empty($this->password)) { + $args = array_merge($args, array("password" => $this->password)); + } + if (!empty($this->token)) { + $args = array_merge($args, array("auth_token" => $this->token)); + } elseif (!empty($_SESSION['phpFlickr_auth_token'])) { + $args = array_merge($args, array("auth_token" => $_SESSION['phpFlickr_auth_token'])); + } + + ksort($args); + $auth_sig = ""; + foreach ($args as $key => $data) { + if ($data !== null) { + $auth_sig .= $key . $data; + $upload_req->addPostData($key, $data); + } + } + if (!empty($this->secret)) { + $api_sig = md5($this->secret . $auth_sig); + $upload_req->addPostData("api_sig", $api_sig); + } + + $photo = realpath($photo); + + $result = $upload_req->addFile("photo", $photo); + + if (PEAR::isError($result)) { + die($result->getMessage()); + } + + //Send Requests + if ($upload_req->sendRequest()) { + $this->response = $upload_req->getResponseBody(); + } else { + die("There has been a problem sending your command to the server."); + } + + $rsp = explode("\n", $this->response); + foreach ($rsp as $line) { + if (ereg('die_on_error) + die("The Flickr API returned the following error: #{$match[1]} - {$match[2]}"); + else { + $this->error_code = $match[1]; + $this->error_msg = $match[2]; + $this->parsed_response = false; + return false; + } + } elseif (ereg("(.*)", $line, $match)) { + $this->error_code = false; + $this->error_msg = false; + return $match[1]; + } + } + } + + function async_upload ($photo, $title = null, $description = null, $tags = null, $is_public = null, $is_friend = null, $is_family = null) { + $upload_req =& new HTTP_Request(); + $upload_req->setMethod(HTTP_REQUEST_METHOD_POST); + + $upload_req->setURL($this->Upload); + $upload_req->clearPostData(); + + //Process arguments, including method and login data. + $args = array("async" => 1, "api_key" => $this->api_key, "title" => $title, "description" => $description, "tags" => $tags, "is_public" => $is_public, "is_friend" => $is_friend, "is_family" => $is_family); + if (!empty($this->email)) { + $args = array_merge($args, array("email" => $this->email)); + } + if (!empty($this->password)) { + $args = array_merge($args, array("password" => $this->password)); + } + if (!empty($this->token)) { + $args = array_merge($args, array("auth_token" => $this->token)); + } elseif (!empty($_SESSION['phpFlickr_auth_token'])) { + $args = array_merge($args, array("auth_token" => $_SESSION['phpFlickr_auth_token'])); + } + + ksort($args); + $auth_sig = ""; + foreach ($args as $key => $data) { + if ($data !== null) { + $auth_sig .= $key . $data; + $upload_req->addPostData($key, $data); + } + } + if (!empty($this->secret)) { + $api_sig = md5($this->secret . $auth_sig); + $upload_req->addPostData("api_sig", $api_sig); + } + + $photo = realpath($photo); + + $result = $upload_req->addFile("photo", $photo); + + if (PEAR::isError($result)) { + die($result->getMessage()); + } + + //Send Requests + if ($upload_req->sendRequest()) { + $this->response = $upload_req->getResponseBody(); + } else { + die("There has been a problem sending your command to the server."); + } + + $rsp = explode("\n", $this->response); + foreach ($rsp as $line) { + if (ereg('die_on_error) + die("The Flickr API returned the following error: #{$match[1]} - {$match[2]}"); + else { + $this->error_code = $match[1]; + $this->error_msg = $match[2]; + $this->parsed_response = false; + return false; + } + } elseif (ereg("(.*)error_code = false; + $this->error_msg = false; + return $match[1]; + } + } + } + + // Interface for new replace API method. + function replace ($photo, $photo_id, $async = null) { + $upload_req =& new HTTP_Request(); + $upload_req->setMethod(HTTP_REQUEST_METHOD_POST); + + $upload_req->setURL($this->Replace); + $upload_req->clearPostData(); + + //Process arguments, including method and login data. + $args = array("api_key" => $this->api_key, "photo_id" => $photo_id, "async" => $async); + if (!empty($this->email)) { + $args = array_merge($args, array("email" => $this->email)); + } + if (!empty($this->password)) { + $args = array_merge($args, array("password" => $this->password)); + } + if (!empty($this->token)) { + $args = array_merge($args, array("auth_token" => $this->token)); + } elseif (!empty($_SESSION['phpFlickr_auth_token'])) { + $args = array_merge($args, array("auth_token" => $_SESSION['phpFlickr_auth_token'])); + } + + ksort($args); + $auth_sig = ""; + foreach ($args as $key => $data) { + if ($data !== null) { + $auth_sig .= $key . $data; + $upload_req->addPostData($key, $data); + } + } + if (!empty($this->secret)) { + $api_sig = md5($this->secret . $auth_sig); + $upload_req->addPostData("api_sig", $api_sig); + } + + $photo = realpath($photo); + + $result = $upload_req->addFile("photo", $photo); + + if (PEAR::isError($result)) { + die($result->getMessage()); + } + + //Send Requests + if ($upload_req->sendRequest()) { + $this->response = $upload_req->getResponseBody(); + } else { + die("There has been a problem sending your command to the server."); + } + if ($async == 1) + $find = 'ticketid'; + else + $find = 'photoid'; + + $rsp = explode("\n", $this->response); + foreach ($rsp as $line) { + if (ereg('die_on_error) + die("The Flickr API returned the following error: #{$match[1]} - {$match[2]}"); + else { + $this->error_code = $match[1]; + $this->error_msg = $match[2]; + $this->parsed_response = false; + return false; + } + } elseif (ereg("<" . $find . ">(.*)error_code = false; + $this->error_msg = false; + return $match[1]; + } + } + } + + function auth ($perms = "read", $remember_uri = true) + { + // Redirects to Flickr's authentication piece if there is no valid token. + // If remember_uri is set to false, the callback script (included) will + // redirect to its default page. + + if (empty($_SESSION['phpFlickr_auth_token']) && empty($this->token)) { + if ($remember_uri) { + $redirect = $_SERVER['REQUEST_URI']; + } + $api_sig = md5($this->secret . "api_key" . $this->api_key . "extra" . $redirect . "perms" . $perms); + if ($this->service == "23") { + header("Location: https://www.23hq.com/services/auth/?api_key=" . $this->api_key . "&extra=" . $redirect . "&perms=" . $perms . "&api_sig=". $api_sig); + } else { + header("Location: https://www.flickr.com/services/auth/?api_key=" . $this->api_key . "&extra=" . $redirect . "&perms=" . $perms . "&api_sig=". $api_sig); + } + exit; + } else { + $tmp = $this->die_on_error; + $this->die_on_error = false; + $rsp = $this->auth_checkToken(); + if ($this->error_code !== false) { + unset($_SESSION['phpFlickr_auth_token']); + $this->auth($perms, $remember_uri); + } + $this->die_on_error = $tmp; + return $rsp['perms']; + } + } + + /******************************* + + To use the phpFlickr::call method, pass a string containing the API method you want + to use and an associative array of arguments. For example: + $result = $f->call("flickr.photos.comments.getList", array("photo_id"=>'34952612')); + This method will allow you to make calls to arbitrary methods that haven't been + implemented in phpFlickr yet. + + *******************************/ + + function call($method, $arguments) + { + $this->request($method, $arguments); + return $this->parsed_response; + } + + /* + These functions are the direct implementations of flickr calls. + For method documentation, including arguments, visit the address + included in a comment in the function. + */ + + /* Activity methods */ + function activity_userComments ($per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.activity.userComments.html */ + $this->request('flickr.activity.userComments', array("per_page" => $per_page, "page" => $page)); + return $this->parsed_response ? $this->parsed_response['items']['item'] : false; + } + + function activity_userPhotos ($timeframe = NULL, $per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.activity.userPhotos.html */ + $this->request('flickr.activity.userPhotos', array("timeframe" => $timeframe, "per_page" => $per_page, "page" => $page)); + return $this->parsed_response ? $this->parsed_response['items']['item'] : false; + } + + /* Authentication methods */ + function auth_checkToken () + { + /* https://www.flickr.com/services/api/flickr.auth.checkToken.html */ + $this->request('flickr.auth.checkToken'); + return $this->parsed_response ? $this->parsed_response['auth'] : false; + } + + function auth_getFrob () + { + /* https://www.flickr.com/services/api/flickr.auth.getFrob.html */ + $this->request('flickr.auth.getFrob'); + return $this->parsed_response ? $this->parsed_response['frob'] : false; + } + + function auth_getFullToken ($mini_token) + { + /* https://www.flickr.com/services/api/flickr.auth.getFullToken.html */ + $this->request('flickr.auth.getFullToken', array('mini_token'=>$mini_token)); + return $this->parsed_response ? $this->parsed_response['auth'] : false; + } + + function auth_getToken ($frob) + { + /* https://www.flickr.com/services/api/flickr.auth.getToken.html */ + $this->request('flickr.auth.getToken', array('frob'=>$frob)); + session_register('phpFlickr_auth_token'); + $_SESSION['phpFlickr_auth_token'] = $this->parsed_response['auth']['token']; + return $this->parsed_response ? $this->parsed_response['auth'] : false; + } + + /* Blogs methods */ + function blogs_getList () + { + /* https://www.flickr.com/services/api/flickr.blogs.getList.html */ + $this->request('flickr.blogs.getList'); + return $this->parsed_response ? $this->parsed_response['blogs']['blog'] : false; + } + + function blogs_postPhoto($blog_id, $photo_id, $title, $description, $blog_password = NULL) + { + /* https://www.flickr.com/services/api/flickr.blogs.postPhoto.html */ + $this->request('flickr.blogs.postPhoto', array('blog_id'=>$blog_id, 'photo_id'=>$photo_id, 'title'=>$title, 'description'=>$description, 'blog_password'=>$blog_password), TRUE); + return $this->parsed_response ? true : false; + } + + /* Contacts Methods */ + function contacts_getList ($filter = NULL, $page = NULL, $per_page = NULL) + { + /* https://www.flickr.com/services/api/flickr.contacts.getList.html */ + $this->request('flickr.contacts.getList', array('filter'=>$filter, 'page'=>$page, 'per_page'=>$per_page)); + return $this->parsed_response ? $this->parsed_response['contacts'] : false; + } + + function contacts_getPublicList($user_id, $page = NULL, $per_page = NULL) + { + /* https://www.flickr.com/services/api/flickr.contacts.getPublicList.html */ + $this->request('flickr.contacts.getPublicList', array('user_id'=>$user_id, 'page'=>$page, 'per_page'=>$per_page)); + return $this->parsed_response ? $this->parsed_response['contacts'] : false; + } + + /* Favorites Methods */ + function favorites_add ($photo_id) + { + /* https://www.flickr.com/services/api/flickr.favorites.add.html */ + $this->request('flickr.favorites.add', array('photo_id'=>$photo_id), TRUE); + return $this->parsed_response ? true : false; + } + + function favorites_getList($user_id = NULL, $extras = NULL, $per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.favorites.getList.html */ + if (is_array($extras)) { $extras = implode(",", $extras); } + $this->request("flickr.favorites.getList", array("user_id"=>$user_id, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page)); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function favorites_getPublicList($user_id = NULL, $extras = NULL, $per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.favorites.getPublicList.html */ + if (is_array($extras)) { + $extras = implode(",", $extras); + } + $this->request("flickr.favorites.getPublicList", array("user_id"=>$user_id, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page)); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function favorites_remove($photo_id) + { + /* https://www.flickr.com/services/api/flickr.favorites.remove.html */ + $this->request("flickr.favorites.remove", array("photo_id"=>$photo_id), TRUE); + return $this->parsed_response ? true : false; + } + + /* Groups Methods */ + function groups_browse ($cat_id = NULL) + { + /* https://www.flickr.com/services/api/flickr.groups.browse.html */ + $this->request("flickr.groups.browse", array("cat_id"=>$cat_id)); + return $this->parsed_response ? $this->parsed_response['category'] : false; + } + + function groups_getInfo ($group_id) + { + /* https://www.flickr.com/services/api/flickr.groups.getInfo.html */ + $this->request("flickr.groups.getInfo", array("group_id"=>$group_id)); + return $this->parsed_response ? $this->parsed_response['group'] : false; + } + + function groups_search ($text, $per_page=NULL, $page=NULL) + { + /* https://www.flickr.com/services/api/flickr.groups.search.html */ + $this->request("flickr.groups.search", array("text"=>$text,"per_page"=>$per_page,"page"=>$page)); + return $this->parsed_response ? $this->parsed_response['groups'] : false; + } + + /* Groups Pools Methods */ + function groups_pools_add ($photo_id, $group_id) + { + /* https://www.flickr.com/services/api/flickr.groups.pools.add.html */ + $this->request("flickr.groups.pools.add", array("photo_id"=>$photo_id, "group_id"=>$group_id), TRUE); + return $this->parsed_response ? true : false; + } + + function groups_pools_getContext ($photo_id, $group_id) + { + /* https://www.flickr.com/services/api/flickr.groups.pools.getContext.html */ + $this->request("flickr.groups.pools.getContext", array("photo_id"=>$photo_id, "group_id"=>$group_id)); + return $this->parsed_response ? $this->parsed_response : false; + } + + function groups_pools_getGroups ($page = NULL, $per_page = NULL) + { + /* https://www.flickr.com/services/api/flickr.groups.pools.getGroups.html */ + $this->request("flickr.groups.pools.getGroups", array('page'=>$page, 'per_page'=>$per_page)); + return $this->parsed_response ? $this->parsed_response['groups'] : false; + } + + function groups_pools_getPhotos ($group_id, $tags = NULL, $user_id = NULL, $extras = NULL, $per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.groups.pools.getPhotos.html */ + if (is_array($extras)) { + $extras = implode(",", $extras); + } + $this->request("flickr.groups.pools.getPhotos", array("group_id"=>$group_id, "tags"=>$tags, "user_id"=>$user_id, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page)); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function groups_pools_remove ($photo_id, $group_id) + { + /* https://www.flickr.com/services/api/flickr.groups.pools.remove.html */ + $this->request("flickr.groups.pools.remove", array("photo_id"=>$photo_id, "group_id"=>$group_id), TRUE); + return $this->parsed_response ? true : false; + } + + /* Interestingness methods */ + function interestingness_getList($date = NULL, $extras = NULL, $per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.interestingness.getList.html */ + if (is_array($extras)) { + $extras = implode(",", $extras); + } + + $this->request("flickr.interestingness.getList", array("date"=>$date, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page)); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + /* People methods */ + function people_findByEmail ($find_email) + { + /* https://www.flickr.com/services/api/flickr.people.findByEmail.html */ + $this->request("flickr.people.findByEmail", array("find_email"=>$find_email)); + return $this->parsed_response ? $this->parsed_response['user'] : false; + } + + function people_findByUsername ($username) + { + /* https://www.flickr.com/services/api/flickr.people.findByUsername.html */ + $this->request("flickr.people.findByUsername", array("username"=>$username)); + return $this->parsed_response ? $this->parsed_response['user'] : false; + } + + function people_getInfo($user_id) + { + /* https://www.flickr.com/services/api/flickr.people.getInfo.html */ + $this->request("flickr.people.getInfo", array("user_id"=>$user_id)); + return $this->parsed_response ? $this->parsed_response['person'] : false; + } + + function people_getPublicGroups($user_id) + { + /* https://www.flickr.com/services/api/flickr.people.getPublicGroups.html */ + $this->request("flickr.people.getPublicGroups", array("user_id"=>$user_id)); + return $this->parsed_response ? $this->parsed_response['groups']['group'] : false; + } + + function people_getPublicPhotos($user_id, $extras = NULL, $per_page = NULL, $page = NULL) { + /* https://www.flickr.com/services/api/flickr.people.getPublicPhotos.html */ + if (is_array($extras)) { + $extras = implode(",", $extras); + } + + $this->request("flickr.people.getPublicPhotos", array("user_id"=>$user_id, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page)); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function people_getUploadStatus() + { + /* https://www.flickr.com/services/api/flickr.people.getUploadStatus.html */ + /* Requires Authentication */ + $this->request("flickr.people.getUploadStatus"); + return $this->parsed_response ? $this->parsed_response['user'] : false; + } + + + /* Photos Methods */ + function photos_addTags ($photo_id, $tags) + { + /* https://www.flickr.com/services/api/flickr.photos.addTags.html */ + $this->request("flickr.photos.addTags", array("photo_id"=>$photo_id, "tags"=>$tags), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_delete($photo_id) + { + /* https://www.flickr.com/services/api/flickr.photos.delete.html */ + $this->request("flickr.photos.delete", array("photo_id"=>$photo_id), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_getAllContexts ($photo_id) + { + /* https://www.flickr.com/services/api/flickr.photos.getAllContexts.html */ + $this->request("flickr.photos.getAllContexts", array("photo_id"=>$photo_id)); + return $this->parsed_response ? $this->parsed_response : false; + } + + function photos_getContactsPhotos ($count = NULL, $just_friends = NULL, $single_photo = NULL, $include_self = NULL, $extras = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.getContactsPhotos.html */ + $this->request("flickr.photos.getContactsPhotos", array("count"=>$count, "just_friends"=>$just_friends, "single_photo"=>$single_photo, "include_self"=>$include_self, "extras"=>$extras)); + return $this->parsed_response ? $this->parsed_response['photos']['photo'] : false; + } + + function photos_getContactsPublicPhotos ($user_id, $count = NULL, $just_friends = NULL, $single_photo = NULL, $include_self = NULL, $extras = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.getContactsPublicPhotos.html */ + $this->request("flickr.photos.getContactsPublicPhotos", array("user_id"=>$user_id, "count"=>$count, "just_friends"=>$just_friends, "single_photo"=>$single_photo, "include_self"=>$include_self, "extras"=>$extras)); + return $this->parsed_response ? $this->parsed_response['photos']['photo'] : false; + } + + function photos_getContext ($photo_id) + { + /* https://www.flickr.com/services/api/flickr.photos.getContext.html */ + $this->request("flickr.photos.getContext", array("photo_id"=>$photo_id)); + return $this->parsed_response ? $this->parsed_response : false; + } + + function photos_getCounts ($dates = NULL, $taken_dates = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.getCounts.html */ + $this->request("flickr.photos.getCounts", array("dates"=>$dates, "taken_dates"=>$taken_dates)); + return $this->parsed_response ? $this->parsed_response['photocounts']['photocount'] : false; + } + + function photos_getExif ($photo_id, $secret = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.getExif.html */ + $this->request("flickr.photos.getExif", array("photo_id"=>$photo_id, "secret"=>$secret)); + return $this->parsed_response ? $this->parsed_response['photo'] : false; + } + + function photos_getFavorites($photo_id, $page = NULL, $per_page = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.getFavorites.html */ + $this->request("flickr.photos.getFavorites", array("photo_id"=>$photo_id, "page"=>$page, "per_page"=>$per_page)); + return $this->parsed_response ? $this->parsed_response['photo'] : false; + } + + function photos_getInfo($photo_id, $secret = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.getInfo.html */ + $this->request("flickr.photos.getInfo", array("photo_id"=>$photo_id, "secret"=>$secret)); + return $this->parsed_response ? $this->parsed_response['photo'] : false; + } + + function photos_getNotInSet($min_upload_date = NULL, $max_upload_date = NULL, $min_taken_date = NULL, $max_taken_date = NULL, $privacy_filter = NULL, $extras = NULL, $per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.getNotInSet.html */ + if (is_array($extras)) { + $extras = implode(",", $extras); + } + $this->request("flickr.photos.getNotInSet", array("min_upload_date"=>$min_upload_date, "max_upload_date"=>$max_upload_date, "min_taken_date"=>$min_taken_date, "max_taken_date"=>$max_taken_date, "privacy_filter"=>$privacy_filter, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page)); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function photos_getPerms($photo_id) + { + /* https://www.flickr.com/services/api/flickr.photos.getPerms.html */ + $this->request("flickr.photos.getPerms", array("photo_id"=>$photo_id)); + return $this->parsed_response ? $this->parsed_response['perms'] : false; + } + + function photos_getRecent($extras = NULL, $per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.getRecent.html */ + + if (is_array($extras)) { + $extras = implode(",", $extras); + } + $this->request("flickr.photos.getRecent", array("extras"=>$extras, "per_page"=>$per_page, "page"=>$page)); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function photos_getSizes($photo_id) + { + /* https://www.flickr.com/services/api/flickr.photos.getSizes.html */ + $this->request("flickr.photos.getSizes", array("photo_id"=>$photo_id)); + return $this->parsed_response ? $this->parsed_response['sizes']['size'] : false; + } + + function photos_getUntagged($min_upload_date = NULL, $max_upload_date = NULL, $min_taken_date = NULL, $max_taken_date = NULL, $privacy_filter = NULL, $extras = NULL, $per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.getUntagged.html */ + if (is_array($extras)) { + $extras = implode(",", $extras); + } + $this->request("flickr.photos.getUntagged", array("min_upload_date"=>$min_upload_date, "max_upload_date"=>$max_upload_date, "min_taken_date"=>$min_taken_date, "max_taken_date"=>$max_taken_date, "privacy_filter"=>$privacy_filter, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page)); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function photos_getWithGeoData($args = NULL) { + /* See the documentation included with the photos_search() function. + * I'm using the same style of arguments for this function. The only + * difference here is that this doesn't require any arguments. The + * flickr.photos.search method requires at least one search parameter. + */ + /* https://www.flickr.com/services/api/flickr.photos.getWithGeoData.html */ + if (is_null($args)) { + $args = array(); + } + $this->request("flickr.photos.getWithGeoData", $args); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function photos_getWithoutGeoData($args = NULL) { + /* See the documentation included with the photos_search() function. + * I'm using the same style of arguments for this function. The only + * difference here is that this doesn't require any arguments. The + * flickr.photos.search method requires at least one search parameter. + */ + /* https://www.flickr.com/services/api/flickr.photos.getWithoutGeoData.html */ + if (is_null($args)) { + $args = array(); + } + $this->request("flickr.photos.getWithoutGeoData", $args); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function photos_recentlyUpdated($min_date = NULL, $extras = NULL, $per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.getUntagged.html */ + if (is_array($extras)) { + $extras = implode(",", $extras); + } + $this->request("flickr.photos.recentlyUpdated", array("min_date"=>$min_date, "extras"=>$extras, "per_page"=>$per_page, "page"=>$page)); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function photos_removeTag($tag_id) + { + /* https://www.flickr.com/services/api/flickr.photos.removeTag.html */ + $this->request("flickr.photos.removeTag", array("tag_id"=>$tag_id), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_search($args) + { + /* This function strays from the method of arguments that I've + * used in the other functions for the fact that there are just + * so many arguments to this API method. What you'll need to do + * is pass an associative array to the function containing the + * arguments you want to pass to the API. For example: + * $photos = $f->photos_search(array("tags"=>"brown,cow", "tag_mode"=>"any")); + * This will return photos tagged with either "brown" or "cow" + * or both. See the API documentation (link below) for a full + * list of arguments. + */ + + /* https://www.flickr.com/services/api/flickr.photos.search.html */ + $this->request("flickr.photos.search", $args); + return $this->parsed_response ? $this->parsed_response['photos'] : false; + } + + function photos_setDates($photo_id, $date_posted = NULL, $date_taken = NULL, $date_taken_granularity = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.setDates.html */ + $this->request("flickr.photos.setDates", array("photo_id"=>$photo_id, "date_posted"=>$date_posted, "date_taken"=>$date_taken, "date_taken_granularity"=>$date_taken_granularity), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_setMeta($photo_id, $title, $description) + { + /* https://www.flickr.com/services/api/flickr.photos.setMeta.html */ + $this->request("flickr.photos.setMeta", array("photo_id"=>$photo_id, "title"=>$title, "description"=>$description), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_setPerms($photo_id, $is_public, $is_friend, $is_family, $perm_comment, $perm_addmeta) + { + /* https://www.flickr.com/services/api/flickr.photos.setPerms.html */ + $this->request("flickr.photos.setPerms", array("photo_id"=>$photo_id, "is_public"=>$is_public, "is_friend"=>$is_friend, "is_family"=>$is_family, "perm_comment"=>$perm_comment, "perm_addmeta"=>$perm_addmeta), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_setTags($photo_id, $tags) + { + /* https://www.flickr.com/services/api/flickr.photos.setTags.html */ + $this->request("flickr.photos.setTags", array("photo_id"=>$photo_id, "tags"=>$tags), TRUE); + return $this->parsed_response ? true : false; + } + + /* Photos - Comments Methods */ + function photos_comments_addComment($photo_id, $comment_text) { + /* https://www.flickr.com/services/api/flickr.photos.comments.addComment.html */ + $this->request("flickr.photos.comments.addComment", array("photo_id" => $photo_id, "comment_text"=>$comment_text), TRUE); + return $this->parsed_response ? $this->parsed_response['comment'] : false; + } + + function photos_comments_deleteComment($comment_id) { + /* https://www.flickr.com/services/api/flickr.photos.comments.deleteComment.html */ + $this->request("flickr.photos.comments.deleteComment", array("comment_id" => $comment_id), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_comments_editComment($comment_id, $comment_text) { + /* https://www.flickr.com/services/api/flickr.photos.comments.editComment.html */ + $this->request("flickr.photos.comments.editComment", array("comment_id" => $comment_id, "comment_text"=>$comment_text), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_comments_getList($photo_id) + { + /* https://www.flickr.com/services/api/flickr.photos.comments.getList.html */ + $this->request("flickr.photos.comments.getList", array("photo_id"=>$photo_id)); + return $this->parsed_response ? $this->parsed_response['comments'] : false; + } + + /* Photos - Geo Methods */ + function photos_geo_getLocation($photo_id) + { + /* https://www.flickr.com/services/api/flickr.photos.geo.getLocation.html */ + $this->request("flickr.photos.geo.getLocation", array("photo_id"=>$photo_id)); + return $this->parsed_response ? $this->parsed_response['photo'] : false; + } + + function photos_geo_getPerms($photo_id) + { + /* https://www.flickr.com/services/api/flickr.photos.geo.getPerms.html */ + $this->request("flickr.photos.geo.getPerms", array("photo_id"=>$photo_id)); + return $this->parsed_response ? $this->parsed_response['perms'] : false; + } + + function photos_geo_removeLocation($photo_id) + { + /* https://www.flickr.com/services/api/flickr.photos.geo.removeLocation.html */ + $this->request("flickr.photos.geo.removeLocation", array("photo_id"=>$photo_id), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_geo_setLocation($photo_id, $lat, $lon, $accuracy = NULL) + { + /* https://www.flickr.com/services/api/flickr.photos.geo.setLocation.html */ + $this->request("flickr.photos.geo.setLocation", array("photo_id"=>$photo_id, "lat"=>$lat, "lon"=>$lon, "accuracy"=>$accuracy), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_geo_setPerms($photo_id, $is_public, $is_contact, $is_friend, $is_family) + { + /* https://www.flickr.com/services/api/flickr.photos.geo.setPerms.html */ + $this->request("flickr.photos.geo.setPerms", array("photo_id"=>$photo_id, "is_public"=>$is_public, "is_contact"=>$is_contact, "is_friend"=>$is_friend, "is_family"=>$is_family), TRUE); + return $this->parsed_response ? true : false; + } + + /* Photos - Licenses Methods */ + function photos_licenses_getInfo() + { + /* https://www.flickr.com/services/api/flickr.photos.licenses.getInfo.html */ + $this->request("flickr.photos.licenses.getInfo"); + return $this->parsed_response ? $this->parsed_response['licenses']['license'] : false; + } + + function photos_licenses_setLicense($photo_id, $license_id) + { + /* https://www.flickr.com/services/api/flickr.photos.licenses.setLicense.html */ + /* Requires Authentication */ + $this->request("flickr.photos.licenses.setLicense", array("photo_id"=>$photo_id, "license_id"=>$license_id), TRUE); + return $this->parsed_response ? true : false; + } + + /* Photos - Notes Methods */ + function photos_notes_add($photo_id, $note_x, $note_y, $note_w, $note_h, $note_text) + { + /* https://www.flickr.com/services/api/flickr.photos.notes.add.html */ + $this->request("flickr.photos.notes.add", array("photo_id" => $photo_id, "note_x" => $note_x, "note_y" => $note_y, "note_w" => $note_w, "note_h" => $note_h, "note_text" => $note_text), TRUE); + return $this->parsed_response ? $this->parsed_response['note'] : false; + } + + function photos_notes_delete($note_id) + { + /* https://www.flickr.com/services/api/flickr.photos.notes.delete.html */ + $this->request("flickr.photos.notes.delete", array("note_id" => $note_id), TRUE); + return $this->parsed_response ? true : false; + } + + function photos_notes_edit($note_id, $note_x, $note_y, $note_w, $note_h, $note_text) + { + /* https://www.flickr.com/services/api/flickr.photos.notes.edit.html */ + $this->request("flickr.photos.notes.edit", array("note_id" => $note_id, "note_x" => $note_x, "note_y" => $note_y, "note_w" => $note_w, "note_h" => $note_h, "note_text" => $note_text), TRUE); + return $this->parsed_response ? true : false; + } + + /* Photos - Transform Methods */ + function photos_transform_rotate($photo_id, $degrees) + { + /* https://www.flickr.com/services/api/flickr.photos.transform.rotate.html */ + $this->request("flickr.photos.transform.rotate", array("photo_id" => $photo_id, "degrees" => $degrees), TRUE); + return $this->parsed_response ? true : false; + } + + /* Photos - Upload Methods */ + function photos_upload_checkTickets($tickets) + { + /* https://www.flickr.com/services/api/flickr.photos.upload.checkTickets.html */ + if (is_array($tickets)) { + $tickets = implode(",", $tickets); + } + $this->request("flickr.photos.upload.checkTickets", array("tickets" => $tickets), TRUE); + return $this->parsed_response ? $this->parsed_response['uploader']['ticket'] : false; + } + + /* Photosets Methods */ + function photosets_addPhoto($photoset_id, $photo_id) + { + /* https://www.flickr.com/services/api/flickr.photosets.addPhoto.html */ + $this->request("flickr.photosets.addPhoto", array("photoset_id" => $photoset_id, "photo_id" => $photo_id), TRUE); + return $this->parsed_response ? true : false; + } + + function photosets_create($title, $description, $primary_photo_id) + { + /* https://www.flickr.com/services/api/flickr.photosets.create.html */ + $this->request("flickr.photosets.create", array("title" => $title, "primary_photo_id" => $primary_photo_id, "description" => $description), TRUE); + return $this->parsed_response ? $this->parsed_response['photoset'] : false; + } + + function photosets_delete($photoset_id) + { + /* https://www.flickr.com/services/api/flickr.photosets.delete.html */ + $this->request("flickr.photosets.delete", array("photoset_id" => $photoset_id), TRUE); + return $this->parsed_response ? true : false; + } + + function photosets_editMeta($photoset_id, $title, $description = NULL) + { + /* https://www.flickr.com/services/api/flickr.photosets.editMeta.html */ + $this->request("flickr.photosets.editMeta", array("photoset_id" => $photoset_id, "title" => $title, "description" => $description), TRUE); + return $this->parsed_response ? true : false; + } + + function photosets_editPhotos($photoset_id, $primary_photo_id, $photo_ids) + { + /* https://www.flickr.com/services/api/flickr.photosets.editPhotos.html */ + $this->request("flickr.photosets.editPhotos", array("photoset_id" => $photoset_id, "primary_photo_id" => $primary_photo_id, "photo_ids" => $photo_ids), TRUE); + return $this->parsed_response ? true : false; + } + + function photosets_getContext($photo_id, $photoset_id) + { + /* https://www.flickr.com/services/api/flickr.photosets.getContext.html */ + $this->request("flickr.photosets.getContext", array("photo_id" => $photo_id, "photoset_id" => $photoset_id)); + return $this->parsed_response ? $this->parsed_response : false; + } + + function photosets_getInfo($photoset_id) + { + /* https://www.flickr.com/services/api/flickr.photosets.getInfo.html */ + $this->request("flickr.photosets.getInfo", array("photoset_id" => $photoset_id)); + return $this->parsed_response ? $this->parsed_response['photoset'] : false; + } + + function photosets_getList($user_id = NULL) + { + /* https://www.flickr.com/services/api/flickr.photosets.getList.html */ + $this->request("flickr.photosets.getList", array("user_id" => $user_id)); + return $this->parsed_response ? $this->parsed_response['photosets'] : false; + } + + function photosets_getPhotos($photoset_id, $extras = NULL, $privacy_filter = NULL, $per_page = NULL, $page = NULL) + { + /* https://www.flickr.com/services/api/flickr.photosets.getPhotos.html */ + $this->request("flickr.photosets.getPhotos", array("photoset_id" => $photoset_id, "extras" => $extras, "privacy_filter" => $privacy_filter, "per_page" => $per_page, "page" => $page)); + return $this->parsed_response ? $this->parsed_response['photoset'] : false; + } + + function photosets_orderSets($photoset_ids) + { + /* https://www.flickr.com/services/api/flickr.photosets.orderSets.html */ + if (is_array($photoset_ids)) { + $photoset_ids = implode(",", $photoset_ids); + } + $this->request("flickr.photosets.orderSets", array("photoset_ids" => $photoset_ids), TRUE); + return $this->parsed_response ? true : false; + } + + function photosets_removePhoto($photoset_id, $photo_id) + { + /* https://www.flickr.com/services/api/flickr.photosets.removePhoto.html */ + $this->request("flickr.photosets.removePhoto", array("photoset_id" => $photoset_id, "photo_id" => $photo_id), TRUE); + return $this->parsed_response ? true : false; + } + + /* Photosets Comments Methods */ + function photosets_comments_addComment($photoset_id, $comment_text) { + /* https://www.flickr.com/services/api/flickr.photosets.comments.addComment.html */ + $this->request("flickr.photosets.comments.addComment", array("photoset_id" => $photoset_id, "comment_text"=>$comment_text), TRUE); + return $this->parsed_response ? $this->parsed_response['comment'] : false; + } + + function photosets_comments_deleteComment($comment_id) { + /* https://www.flickr.com/services/api/flickr.photosets.comments.deleteComment.html */ + $this->request("flickr.photosets.comments.deleteComment", array("comment_id" => $comment_id), TRUE); + return $this->parsed_response ? true : false; + } + + function photosets_comments_editComment($comment_id, $comment_text) { + /* https://www.flickr.com/services/api/flickr.photosets.comments.editComment.html */ + $this->request("flickr.photosets.comments.editComment", array("comment_id" => $comment_id, "comment_text"=>$comment_text), TRUE); + return $this->parsed_response ? true : false; + } + + function photosets_comments_getList($photoset_id) + { + /* https://www.flickr.com/services/api/flickr.photosets.comments.getList.html */ + $this->request("flickr.photosets.comments.getList", array("photoset_id"=>$photoset_id)); + return $this->parsed_response ? $this->parsed_response['comments'] : false; + } + + /* Reflection Methods */ + function reflection_getMethodInfo($method_name) + { + /* https://www.flickr.com/services/api/flickr.reflection.getMethodInfo.html */ + $this->request("flickr.reflection.getMethodInfo", array("method_name" => $method_name)); + return $this->parsed_response ? $this->parsed_response : false; + } + + function reflection_getMethods() + { + /* https://www.flickr.com/services/api/flickr.reflection.getMethods.html */ + $this->request("flickr.reflection.getMethods"); + return $this->parsed_response ? $this->parsed_response['methods']['method'] : false; + } + + /* Tags Methods */ + function tags_getHotList($period = NULL, $count = NULL) + { + /* https://www.flickr.com/services/api/flickr.tags.getHotList.html */ + $this->request("flickr.tags.getHotList", array("period" => $period, "count" => $count)); + return $this->parsed_response ? $this->parsed_response['hottags'] : false; + } + + function tags_getListPhoto($photo_id) + { + /* https://www.flickr.com/services/api/flickr.tags.getListPhoto.html */ + $this->request("flickr.tags.getListPhoto", array("photo_id" => $photo_id)); + return $this->parsed_response ? $this->parsed_response['photo']['tags']['tag'] : false; + } + + function tags_getListUser($user_id = NULL) + { + /* https://www.flickr.com/services/api/flickr.tags.getListUser.html */ + $this->request("flickr.tags.getListUser", array("user_id" => $user_id)); + return $this->parsed_response ? $this->parsed_response['who']['tags']['tag'] : false; + } + + function tags_getListUserPopular($user_id = NULL, $count = NULL) + { + /* https://www.flickr.com/services/api/flickr.tags.getListUserPopular.html */ + $this->request("flickr.tags.getListUserPopular", array("user_id" => $user_id, "count" => $count)); + return $this->parsed_response ? $this->parsed_response['who']['tags']['tag'] : false; + } + + function tags_getListUserRaw($tag) + { + /* https://www.flickr.com/services/api/flickr.tags.getListUserRaw.html */ + $this->request("flickr.tags.getListUserRaw", array("tag" => $tag)); + return $this->parsed_response ? $this->parsed_response['who']['tags']['tag'][0]['raw'] : false; + } + + function tags_getRelated($tag) + { + /* https://www.flickr.com/services/api/flickr.tags.getRelated.html */ + $this->request("flickr.tags.getRelated", array("tag" => $tag)); + return $this->parsed_response ? $this->parsed_response['tags'] : false; + } + + function test_echo($args = array()) + { + /* https://www.flickr.com/services/api/flickr.test.echo.html */ + $this->request("flickr.test.echo", $args); + return $this->parsed_response ? $this->parsed_response : false; + } + + function test_login() + { + /* https://www.flickr.com/services/api/flickr.test.login.html */ + $this->request("flickr.test.login"); + return $this->parsed_response ? $this->parsed_response['user'] : false; + } + + function urls_getGroup($group_id) + { + /* https://www.flickr.com/services/api/flickr.urls.getGroup.html */ + $this->request("flickr.urls.getGroup", array("group_id"=>$group_id)); + return $this->parsed_response ? $this->parsed_response['group']['url'] : false; + } + + function urls_getUserPhotos($user_id = NULL) + { + /* https://www.flickr.com/services/api/flickr.urls.getUserPhotos.html */ + $this->request("flickr.urls.getUserPhotos", array("user_id"=>$user_id)); + return $this->parsed_response ? $this->parsed_response['user']['url'] : false; + } + + function urls_getUserProfile($user_id = NULL) + { + /* https://www.flickr.com/services/api/flickr.urls.getUserProfile.html */ + $this->request("flickr.urls.getUserProfile", array("user_id"=>$user_id)); + return $this->parsed_response ? $this->parsed_response['user']['url'] : false; + } + + function urls_lookupGroup($url) + { + /* https://www.flickr.com/services/api/flickr.urls.lookupGroup.html */ + $this->request("flickr.urls.lookupGroup", array("url"=>$url)); + return $this->parsed_response ? $this->parsed_response['group'] : false; + } + + function urls_lookupUser($url) + { + /* https://www.flickr.com/services/api/flickr.photos.notes.edit.html */ + $this->request("flickr.urls.lookupUser", array("url"=>$url)); + return $this->parsed_response ? $this->parsed_response['user'] : false; + } +} + + +?> diff --git a/index.php b/index.php new file mode 100644 index 0000000..55bab56 --- /dev/null +++ b/index.php @@ -0,0 +1,3 @@ + diff --git a/pages/about.php b/pages/about.php new file mode 100644 index 0000000..aff289b --- /dev/null +++ b/pages/about.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/pages/favorites.php b/pages/favorites.php new file mode 100644 index 0000000..4834884 --- /dev/null +++ b/pages/favorites.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/pages/group.php b/pages/group.php new file mode 100644 index 0000000..202f961 --- /dev/null +++ b/pages/group.php @@ -0,0 +1,87 @@ +getconst('FLICKR_GROUP_ID'); + + if (!$this->info || $this->info['id'] != $nsid) { + $this->info = $this->phpFlickr->groups_getInfo( $nsid ); + } + return $this->info; + } + + function get_name( $nsid = null ) { + $info = $this->get_info( $nsid ); + return $info['name']; + } + + function name( $nsid = null ) { + echo $this->get_name( $nsid ); + } + + function get_description( $nsid = null ) { + $info = $this->get_info( $nsid ); + return $info['description']; + } + + function description( $nsid = null ) { + echo $this->get_description( $nsid ); + } + + function get_members( $nsid = null ) { + $info = $this->get_info( $nsid ); + return $info['members']; + } + + function members( $nsid = null ) { + echo $this->get_members( $nsid ); + } + + function get_privacy( $nsid = null ) { + $info = $this->get_info( $nsid ); + return $info['privacy']; + } + + function privacy( $nsid = null ) { + echo $this->get_privacy( $nsid ); + } + + function get_icon( $nsid = null ) { + if (!$nsid) $nsid = $this->getconst('FLICKR_GROUP_ID'); + + $info = $this->get_info( $nsid ); + $iconserver = $info['iconserver']; + $iconfarm = $info['iconfarm']; + $iconsrc = ''; + + if ( 0 < $iconserver ) { + $iconsrc = "http://farm{$iconfarm}.static.flickr.com/{$iconserver}/buddyicons/{$nsid}.jpg"; + } else { + $iconsrc = "http://www.flickr.com/images/buddyicon.jpg"; + } + + return ""; + } + + function icon( $nsid = null ) { + echo $this->get_icon( $nsid ); + } + + function get_grouplink( $groupId = null ) { + $p = new Profiler(); + $groupId = $groupId ? $groupId : FLICKR_GROUP_ID; + return "http://www.flickr.com/people/{$groupId}/"; + } + + function grouplink( $groupId = null, $inner = null ) { + $p = new Profiler(); + $inner = $inner ? $inner : $this->get_name($groupId); + echo "{$inner}"; + } + +} +?> \ No newline at end of file diff --git a/pages/map.php b/pages/map.php new file mode 100644 index 0000000..4834884 --- /dev/null +++ b/pages/map.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/pages/map_data.php b/pages/map_data.php new file mode 100644 index 0000000..5cdc209 --- /dev/null +++ b/pages/map_data.php @@ -0,0 +1,31 @@ +formatOutput = true; +$markers = $doc->createElement("markers"); +$doc->appendChild($markers); +require_once( 'photo.php' ); +$photos = $photo->get_photos_with_geo_data(); +foreach ($photos["photo"] as $flickrPhoto) { + $title = $flickrPhoto["title"]; + $desc = $flickrPhoto["description"]; + $lat = $flickrPhoto["latitude"]; + $lng = $flickrPhoto["longitude"]; + $url = "index.php?photoId={$flickrPhoto['id']}"; + $src = $flickrPhoto["url_t"]; + $height = $flickrPhoto["height_t"]; + $width = $flickrPhoto["width_t"]; + $img = ""; + + $marker = $doc->createElement("marker"); + $marker->setAttribute("title", $title); + $marker->setAttribute("lat", $lat); + $marker->setAttribute("lng", $lng); + $marker->setAttribute("url", $url); + + $html = "
{$title}

{$img}

{$desc}
"; + $marker->setAttribute("html", $html); + $markers->appendChild($marker); +} +echo $doc->saveXML(); +?> \ No newline at end of file diff --git a/pages/page.php b/pages/page.php new file mode 100644 index 0000000..17d9ee3 --- /dev/null +++ b/pages/page.php @@ -0,0 +1,246 @@ + + * @version 2.5.7 + * @package Flogr + * @link http://flogr.googlecode.com + */ + +require_once('profiler.php'); + +define('FLOGR_FLICKR_API_KEY', '64735d606d8cc904a3f62d3ed56d56b9'); +define('FLOGR_SIZE', 'thumbnail'); +define('FLOGR_PER_PAGE', 5); +define('FLOGR_PHOTO_EXTRAS', 'description, license, date_upload, date_taken, owner_name, icon_server, original_format, last_update, geo, tags, machine_tags, o_dims, views, media, path_alias, url_sq, url_t, url_s, url_m, url_z, url_l, url_o'); + +/** + * The Flogr_Page:: class is the base class for the flogr page handlers. This class + * manages the phpFlickr instance, the request parameters, and contains some some common + * functionality. + * + * @author Mike Carruth + * @version 2.5.7 + * @package Flogr + * @link http://flogr.googlecode.com + */ +class Flogr_Page { + var $paramPage; + var $paramPerPage; + var $paramPhotoId; + var $paramSetId; + var $paramTags; + var $paramSize; + var $paramSort; + var $phpFlickr; + var $photoList; + + function Flogr_Page() { + + if (!$this->phpFlickr) { + $this->phpFlickr = new phpFlickr(FLOGR_FLICKR_API_KEY); + //$this->phpFlickr->setProxy('157.54.108.18', 80); + + if (CACHE_SQL_USER && + CACHE_SQL_PASSWORD && + CACHE_SQL_SERVER && + CACHE_SQL_DATABASE) { + $mysqlConnection = + 'mysql://' . + CACHE_SQL_USER . ':' . + CACHE_SQL_PASSWORD . '@' . + CACHE_SQL_SERVER . '/' . + CACHE_SQL_DATABASE; + + $this->phpFlickr->enableCache('db', $mysqlConnection); + } + else if (CACHE_PATH) { + $this->phpFlickr->enableCache('fs', CACHE_PATH); + } + } + + $this->paramPage = $_GET['page'] ? $_GET['page'] : 1; + $this->paramPerPage = $_GET['perPage'] ? $_GET['perPage'] : FLOGR_THUMBNAILS_PER_PAGE; + $this->paramPhotoId = $_GET['photoId']; + $this->paramSetId = $_GET['setId']; + $this->paramTags = $_GET['tags'] ? $_GET['tags'] : FLOGR_TAGS_INCLUDE; + $this->paramSize = $_GET['size'] ? $_GET['size'] : FLOGR_SIZE; + $this->paramSort = $_GET['sort'] ? $_GET['sort'] : FLOGR_SORT; + } + + function run_tests() { + if (1 == version_compare( PHP_VERSION, '5.0.0' )) { + + global $flogr; + $class = new ReflectionClass(get_class($this)); + $methods = $class->getMethods(); + $className = $class->getName(); + $methodname = null; + + $totalStartTime = microtime(true); + + echo "

{$className} Tests

"; + print ""; + + foreach ( $methods as $method ) { + $methodName = $method->getName(); + switch ($methodName) { + case "run_tests": + case "photo_slideshow_thumbnails": + case "photo_link_thumbnails": + case "next_page_link": + case "prev_page_link": + case "Flogr_Page": + case $className: + continue 2; + } + + echo ""; + + /* + $message = "{$className}::{$methodName}() took {$callRunTime}s"; + if ($callRunTime < 1) { + $flogr->logInfo($message); + } + else if ($callRunTime < 2) { + $flogr->logWarning($message); + } + else { + $flogr->logErr($message); + } +*/ + } + + print "
" . $method->getName() . ""; + + $callStartTime = microtime(true); + + $return = $method->invoke($this); + if ($return) { + if (is_array($return)) print_r($return); + else echo $return; + } + + $callRunTime = round(microtime(true) - $callStartTime, 2); + + echo "
"; + + $totalRunTime = round(microtime(true) - $totalStartTime, 4); + //$flogr->logInfo("{$className} tests took {$totalRunTime}s"); + } + } + + function get_slideshow_thumbnails( $photos = null ) { + $date = null; + $thumbs = null; + $photos = $photos ? $photos : $photoList; + + if ( $photos['photo'] ) { + foreach ($photos['photo'] as $photo) { + if ( FLOGR_SHOW_DATE_TAKEN && isset( $photo['datetaken'] ) ) { + $date = date( FLOGR_DATE_FORMAT, strtotime( $photo['datetaken'] ) ); + } + else if ( isset( $photo['dateupload'] ) ) { + $date = date( FLOGR_DATE_FORMAT, $photo['dateupload'] ); + } + + $title = htmlspecialchars($photo['title'], ENT_QUOTES); + $titleLink = htmlspecialchars("" . $photo['title'] . " | " . $date . "", ENT_QUOTES); + if ( !$title ) { $title = 'untitled'; } + + $thumbs .= + "" . + "" . + ""; + + } + } + return $thumbs; + } + + function slideshow_thumbnails( $photos = null ) { + $photos = $photos ? $photos : $photoList; + echo $this->get_slideshow_thumbnails( $photos ); + } + + function photo_link_thumbnails( $photos, $link = null ) { + $date = null; + $thumbs = null; + $title = null; + $photos = $photos ? $photos : $photoList; + + if ( $photos['photo'] ) { + foreach ($photos['photo'] as $photo) { + if ( FLOGR_SHOW_DATE_TAKEN && isset( $photo['datetaken'] ) ) { + $date = date( FLOGR_DATE_FORMAT, strtotime( $photo['datetaken'] ) ); + } + else if ( isset( $photo['dateupload'] ) ) { + $date = date( FLOGR_DATE_FORMAT, $photo['dateupload'] ); + } + + $title = htmlspecialchars($photo['title'], ENT_QUOTES); + + $thumbs .= + "" . + "" . + ""; + } + } + + echo $thumbs; + } + + function get_previous_page_link( $photos, $inner = 'prev' ) { + $prevPage = $this->paramPage < $photos['pages'] ? $this->paramPage + 1 : 0; + + if ( $prevPage ) { + $url = "$val) { + if ( $key == "page" ) { + continue; + } + $url .= "$key=$val&"; + } + $url .= "page=$prevPage"; + return $url . ">$inner"; + } + } + + function previous_page_link( $photos, $inner = 'prev' ) { + echo $this->get_previous_page_link( $photos, $inner ); + } + + function get_next_page_link( $photos, $inner = 'next' ) { + $nextPage = 1 < $this->paramPage ? $this->paramPage - 1: 0; + + if ( $nextPage ) { + $url = "$val) { + if ( $key == "page" ) { + continue; + } + $url .= "$key=$val&"; + } + $url .= "page=$nextPage"; + return $url . ">$inner"; + } + } + + function next_page_link( $photos, $inner = 'next' ) { + echo $this->get_next_page_link( $photos, $inner ); + } + + function getconst($const) { + return (defined($const)) ? constant($const) : null; + } +} +?> \ No newline at end of file diff --git a/pages/photo.php b/pages/photo.php new file mode 100644 index 0000000..31ea483 --- /dev/null +++ b/pages/photo.php @@ -0,0 +1,739 @@ + FLICKR_USER_ID, + "group_id" => FLICKR_GROUP_ID, + "tags" => $tags ? $tags : $this->paramTags, + "sort" => $sort ? $sort : $this->paramSort, + "extras" => $extras ? $extras : FLOGR_PHOTO_EXTRAS, + "per_page" => $perPage ? $perPage : $this->paramPerPage, + "page" => $page ? $page : $this->paramPage); + + $this->photoList = $this->phpFlickr->photos_search($photoSearchParams); + return $this->photoList; + } + + function get_photo($photoId = null) { + $p = new Profiler(); + $photoId = $photoId ? $photoId : $this->paramPhotoId; + + if (!$this->photoList || $this->photoList["photo"][0]["id"] != $photoId) { + if ($photoId) { + /* + * If passed a photoId use flickr.photos.getInfo to get the photo's + * title and date taken, then pass those values on to flickr.photos.search + * to get the base $photo properties + */ + $this->info = $this->get_photo_info($photoId); + $userId = $this->info["owner"]["nsid"]; + $photoSearchParams = array( + "user_id" => $userId, + "group_id" => FLICKR_GROUP_ID, + "min_taken_date" => $this->info["dates"]["taken"] . ":00", + "max_taken_date" => $this->info["dates"]["taken"] . ":59", + "sort" => $this->paramSort, + "extras" => FLOGR_PHOTO_EXTRAS, + "per_page" => 1, + "page" => $this->paramPage); + + $this->photoList = $this->phpFlickr->photos_search($photoSearchParams); + } else { + // Get the first photo from the user and/or group using the defaults. + $this->photoList = $this->photoList ? $this->photoList : $this->get_photos(null, null, null, 1, null); + } + } + + return $this->photoList["photo"][0]; + } + + function photos( + $userId = null, $tags = null, $sort = null, $extras = null, $perPage = null, $page = null) { + $p = new Profiler(); + echo $photos = $this->get_photos($userId, $tags, $sort, $extras, $perPage, $page); + } + + function get_photos_with_geo_data( + $tags = null, $sort = null, $extras = null, $perPage = null, $page = null) { + $p = new Profiler(); + $photoSearchParams = array( + "has_geo" => "1", + "user_id" => FLICKR_USER_ID, + "group_id" => FLICKR_GROUP_ID, + "tags" => $tags ? $tags : $this->paramTags, + "sort" => $sort ? $sort : $this->paramSort, + "extras" => $extras ? $extras : FLOGR_PHOTO_EXTRAS); + $this->photoList = $this->phpFlickr->photos_search($photoSearchParams); + return $this->photoList; + } + + function get_user_favorite_photos( + $userId = null, $extras = null, $perPage = null, $page = null) { + $p = new Profiler(); + $this->photoList = $this->phpFlickr->favorites_getPublicList( + $user ? $user : FLICKR_USER_ID, + $extras ? $extras : FLOGR_PHOTO_EXTRAS, + $perPage ? $perPage : $this->paramPerPage, + $page ? $page : $this->paramPage); + return $this->photoList; + } + + function user_favorite_photos( + $userId = null, $extras = null, $perPage = null, $page = null) { + $p = new Profiler(); + echo $this->get_user_favorite_photos($userId, $extras, $perPage, $page); + } + + function get_user_interestingness_photos( + $date = null, $extras = null, $perPage = null, $page = null) { + $p = new Profiler(); + $this->photoList = $this->phpFlickr->interestingness_getList( + $date, + $extras ? $extras : FLOGR_PHOTO_EXTRAS, + $perPage ? $perPage : $this->paramPerPage, + $page ? $page : $this->paramPage); + return $this->photoList; + } + + function user_interestingness_photos( + $date = null, $extras = null, $perPage = null, $page = null) { + $p = new Profiler(); + echo $this->get_user_interestingness_photos($date, $extras, $perPage, $page); + } + + function get_photo_id() { + $p = new Profiler(); + if ($this->paramPhotoId) { + return $this->paramPhotoId; + } else if ($this->photoList) { + return $this->photoList["photo"][0]["id"]; + } else { + $photo = $this->get_photo(); + return $photo["id"]; + } + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @return unknown + */ + function get_photo_info($photoId = null) { + $p = new Profiler(); + $photoId = $photoId ? $photoId : $this->get_photo_id(); + if (!$this->info || !$this->info["id"] != $photoId) { + $this->info = $this->phpFlickr->photos_getInfo($photoId); + } + return $this->info; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @return unknown + */ + function get_username($photoId = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + return $photo["ownername"]; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + */ + function username($photoId = null) { + $p = new Profiler(); + echo $this->get_username($photoId); + } + + function get_userId($photoId = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + return $photo["owner"]; + } + + function get_userlink($photoId = null) { + $p = new Profiler(); + return "http://www.flickr.com/people/{$this->get_userId($photoId)}/"; + } + + function userlink($photoId = null, $inner = null) { + $p = new Profiler(); + $inner = $inner ? $inner : $this->get_username($photoId); + echo "{$inner}"; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @return unknown + */ + function get_realname($photoId = null) { + $p = new Profiler(); + $info = $this->get_photo_info($photoId); + return $info['owner']['realname']; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + */ + function realname($photoId = null) { + $p = new Profiler(); + echo $this->get_realname($photoId); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @return unknown + */ + function get_location($photoId = null) { + $p = new Profiler(); + $info = $this->get_photo_info($photoId); + return $info['owner']['location']; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + */ + function location($photoId = null) { + $p = new Profiler(); + echo $this->get_location($photoId); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $this->phpFlickrormat + * @return unknown + */ + function get_dateposted($photoId = null, $format = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + $format = $format ? $format : FLOGR_DATE_FORMAT; + return date($format, $photo["dateupload"]); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $this->phpFlickrormat + */ + function dateposted($photoId = null, $format = null) { + $p = new Profiler(); + echo $this->get_dateposted($photoId, $format); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $this->phpFlickrormat + * @return unknown + */ + function get_datetaken($photoId = null, $format = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + $format = $format ? $format : FLOGR_DATE_FORMAT; + return date($format, $photo["datetaken"]); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $this->phpFlickrormat + */ + function datetaken($photoId = null, $format = null) { + $p = new Profiler(); + echo $this->get_datetaken($photoId, $format); + } + + function get_exif($photoId = null) { + $p = new Profiler(); + $photoId = $photoId ? $photoId : $this->get_photo_id(); + + if (!$this->exif) { + $this->exif = $this->phpFlickr->photos_getExif($photoId); + } + + return $this->exif; + } + + function exif($photoId = null) { + $p = new Profiler(); + $exif = $this->get_exif($photoId); + $exifData = null; + if ($exif['exif']) { + foreach ($exif['exif'] as $exifItem) { + if (stristr(FLOGR_EXIF, $exifItem['label']) && !stristr($exifData, $exifItem['label'])) { + $exifData .= ""; + $exifData .= "" . $exifItem['label'] . ""; + if ($exifItem['clean']) { + $exifData .= "" . $exifItem['clean'] . ""; + } else { + $exifData .= "" . $exifItem['raw'] . ""; + } + $exifData .= ""; + } + } + echo "$exifData
"; + } + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @return unknown + */ + function get_is_favorite($photoId = null, $truetext = 'Y', $falsetext = 'N') { + $p = new Profiler(); + $info = $this->get_photo_info($photoId); + if ($info['isfavorite']) { + return $truetext; + } else { + return $falsetext; + } + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $truetext + * @param unknown_type $this->phpFlickralsetext + */ + function is_favorite($photoId = null, $truetext = 'Y', $falsetext = 'N') { + $p = new Profiler(); + echo $this->get_is_favorite($photoId, $truetext, $falsetext); + } + + /** + * Enter description here... + * + * @return unknown + */ + function get_licenses() { + $p = new Profiler(); + if (!$this->licenses) { + $this->licenses = $this->phpFlickr->photos_licenses_getInfo(); + } + + return $this->licenses; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @return unknown + */ + function get_license_name($photoId = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + $licenses = $this->get_licenses(); + return $licenses[$photo["license"]]["name"]; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + */ + function license_name($photoId = null) { + $p = new Profiler(); + echo $this->get_license_name($photoId); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $inner + * @return unknown + */ + function get_license_link($photoId = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + $licenses = $this->get_licenses(); + return $licenses[$photo["license"]]["url"]; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $inner + */ + function license_link($photoId = null, $inner = null) { + $p = new Profiler(); + $inner = $inner ? $inner : $this->get_license_name($photoId); + echo "{$inner}"; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @return unknown + */ + function get_title($photoId = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + return htmlspecialchars($photo["title"], ENT_QUOTES); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + */ + function title($photoId = null) { + $p = new Profiler(); + echo $this->get_title($photoId); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @return unknown + */ + function get_description($photoId = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + return htmlspecialchars($photo["description"], ENT_QUOTES); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + */ + function description($photoId = null) { + $p = new Profiler(); + echo $this->get_description($photoId); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $inner + * @return unknown + */ + function get_photopage_link($photoId = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + return "http://www.flickr.com/photos/{$photo['owner']}/{$photo['id']}/"; + } + + function get_permalink($photoId = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + return SITE_URL . "/index.php?photoId={$this->get_photo_id($photoId)}"; + } + + function permalink($photoId = null, $inner = null) { + $p = new Profiler(); + $inner = $inner ? $inner : $this->get_permalink($photoId); + ; + echo "{$inner}"; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $inner + */ + function photopage_link($photoId = null, $inner = null) { + $p = new Profiler(); + $inner = $inner ? $inner : $this->get_photopage_link($photoId); + ; + echo "{$inner}"; + } + + function get_views_count($photoId = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + return $photo["views"]; + } + + function views_count($photoId = null) { + $p = new Profiler(); + echo $this->get_views_count(); + } + + function get_comments($photoId = null) { + $p = new Profiler(); + $photoId = $photoId ? $photoId : $this->get_photo_id(); + + if (!$this->comments) { + $this->comments = $this->phpFlickr->photos_comments_getList($photoId); + } + + return $this->comments; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @return unknown + */ + function get_comments_count($photoId = null) { + $p = new Profiler(); + $comments = $this->get_comments($photoId); + return count($comments["comment"]); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + */ + function comments_count($photoId = null) { + $p = new Profiler(); + echo $this->get_comments_count($photoId); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $before + * @param unknown_type $sep + * @param unknown_type $after + * @param unknown_type $commentLink + * @param unknown_type $buddyIcon + * @return unknown + */ + function get_comments_list( + $photoId = null, $before = '
  • ', $sep = ' says:
    ', $after = '
  • ', $commentLink = 'true', $buddyIcon = 'true') { + $p = new Profiler(); + $comments = $this->get_comments($photoId); + if ($comments['comment']) { + $comment_list = null; + foreach ($comments['comment'] as $comment) { + $commentAuthor = "{$comment['authorname']}"; + $commentText = $comment['_content']; + $commentDate = "
    Posted on: " . date(FLOGR_DATE_FORMAT, $comment['datecreate']) . ""; + $comment_list .= $before . $commentAuthor . $sep . $commentText . $commentDate . $after; + } + } + + if ($commentLink) { + $comment_list .= $this->get_photopage_link($photoId, "
    Leave a comment"); + } + + + return $comment_list; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $before + * @param unknown_type $sep + * @param unknown_type $after + * @param unknown_type $commentLink + * @param unknown_type $buddyIcon + */ + function comments_list($photoId = null, $before = '
  • ', $sep = ' says:
    ', $after = '
  • ', $commentLink = 'true', $buddyIcon = 'true') { + $p = new Profiler(); + echo $this->get_comments_list($photoId, $before, $sep, $after, $commentLink, $buddyIcon); + } + + /** + * Enter description here ... + * @param unknown_type $photoId + * @param unknown_type $text + */ + function comment_add($photoId = null, $text = null) { + $p = new Profiler(); + + $photoId = $photoId ? $photoId : $this->get_photo_id(); + + $this->phpFlickr->photos_comments_addComment($photoId, $text); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $before + * @param unknown_type $after + * @return unknown + */ + function get_tags_list($photoId = null, $before = '
  • ', $after = '
  • ') { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + $tok = strtok($photo["tags"], " "); + $tag_list = ""; + while ($tok !== false) { + $tag_list .= "{$before}{$tok}{$after}"; + $tok = strtok(" "); + } + + return $tag_list; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $before + * @param unknown_type $after + */ + function tags_list($photoId = null, $before = '
  • ', $after = '
  • ') { + $p = new Profiler(); + echo $this->get_tags_list($photoId, $before, $after); + } + + function get_img_src($photoId = null, $quality = FLOGR_PHOTO_QUALITY, $scaleSize = null) { + $photo = $this->get_photo($photoId); + + switch (strtolower($quality)) { + case "square": + $src = $photo["url_sq"]; + $height = $photo["height_sq"]; + $width = $photo["width_sq"]; + break; + case "thumbnail": + $src = $photo["url_t"]; + $height = $photo["height_t"]; + $width = $photo["width_t"]; + break; + case "small": + $src = $photo["url_s"]; + $height = $photo["height_s"]; + $width = $photo["width_s"]; + break; + case "medium640": + if ( $photo["url_z"] ) { + $src = $photo["url_z"]; + $height = $photo["height_z"]; + $width = $photo["width_z"]; + } else { + $src = $photo["url_m"]; + $height = $photo["height_m"]; + $width = $photo["width_m"]; + } + break; + case "medium": + $src = $photo["url_m"]; + $height = $photo["height_m"]; + $width = $photo["width_m"]; + break; + case "large": + $src = $photo["url_l"]; + $height = $photo["height_l"]; + $width = $photo["width_l"]; + break; + default: // Original or custom size + $src = $photo["url_o"]; + $height = $photo["height_o"]; + $width = $photo["width_o"]; + break; + } + + if ($scaleSize) { + if ($width > $height) { + // set width to scaleSize and scale height + $height *= $scaleSize / $width; + $width = $scaleSize; + } else if ($height > $width) { + // set height to scaleSize and scale width + $width *= $scaleSize / $height; + $height = $scaleSize; + } + } + + if ($width > FLOGR_MAIN_PHOTO_SIZE) { + // set width to scaleSize and scale height + $scaleSize = FLOGR_MAIN_PHOTO_SIZE; + $height *= $scaleSize / $width; + $width = $scaleSize; + } + + return array("url" => $src, "height" => $height, "width" => $width); + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $size + * @return unknown + */ + function get_img($photoId = null, $quality = FLOGR_PHOTO_QUALITY, $scaleSize = null) { + $p = new Profiler(); + $src = $this->get_img_src($photoId, $quality, $scaleSize); + $url = $src["url"]; + $height = $src["height"]; + $width = $src["width"]; + $title = $this->get_title($photoId); + $desc = $this->get_description($photoId); + + return ""; + } + + /** + * Enter description here... + * + * @param unknown_type $photoId + * @param unknown_type $quality + * @param unknown_type $scaleSize + */ + function img($photoId = null, $quality = FLOGR_PHOTO_QUALITY, $scaleSize = null) { + $p = new Profiler(); + echo $this->get_img($photoId, $quality, $scaleSize); + } + + function add_comment($id = null, $comment = null) { + $p = new Profiler(); + if ($id == null || $comment == null) + return; + $this->phpFlickr->photos_comments_addComment($id, $comment); + } + + function get_geo_location($id = null) { + $p = new Profiler(); + $photo = $this->get_photo($photoId); + $geo = array("latitude" => $photo["latitude"], "longitude" => $photo["longitude"], "accuracy" => $photo["accuracy"]); + if ($geo["latitude"] !== 0 && $geo["longitude"] !== 0) { + return $geo; + } else { + return null; + } + } + +} +$photo = new Flogr_Photo(); +?> \ No newline at end of file diff --git a/pages/recent.php b/pages/recent.php new file mode 100644 index 0000000..4834884 --- /dev/null +++ b/pages/recent.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/pages/rss.php b/pages/rss.php new file mode 100644 index 0000000..273519f --- /dev/null +++ b/pages/rss.php @@ -0,0 +1,60 @@ +"; +// echo ""; +echo ""; + +require_once('photo.php'); +require_once('user.php'); + +$photos = $photo->get_photos(null, null, null, 10, 1); +$userInfo = $user->get_info(); + +// +// Boiler-plate RSS info +// +echo " + + " . htmlspecialchars(SITE_TITLE , ENT_QUOTES) . " + " . htmlspecialchars(SITE_URL, ENT_QUOTES) . " + " . htmlspecialchars(SITE_DESCRIPTION, ENT_QUOTES) . " + " . date(DATE_RFC2822) . " + " . date(DATE_RFC2822) . " + + + http://farm" . $userInfo['iconfarm'] . ".static.flickr.com/" . $userInfo['iconserver']. "/buddyicons/" . $userInfo['nsid'] . ".jpg + " . htmlspecialchars(SITE_TITLE, ENT_QUOTES) . " + " . htmlspecialchars(SITE_URL, ENT_QUOTES) . " + "; + +// +// Generate items for the last 10 photos +// +foreach ($photos['photo'] as $p) { + $date = $photo->get_dateposted( $p['id'] ); + $rssdate = $photo->get_dateposted( $p['id'], DATE_RFC2822 ); + $photoURL = htmlspecialchars(""); + $photoURL .= htmlspecialchars($photo->get_img($p['id'], "Medium")); + $photoURL .= htmlspecialchars(""); + + echo ""; + echo "" . $photo->get_title( $p['id'] ) . ""; + echo "" . SITE_URL . "/index.php?photoId=" . $p['id'] . ""; + echo "" . $photoURL . "<p>" . $photo->get_description( $p['id'] ) . "</p><p>" . $date . "</p>"; + echo "" . SITE_URL . "/index.php?photoId=" . $p['id'] . ""; + echo "" . $rssdate .""; + echo ""; +} + +echo " + +"; + +?> diff --git a/pages/sets.php b/pages/sets.php new file mode 100644 index 0000000..f5d42e9 --- /dev/null +++ b/pages/sets.php @@ -0,0 +1,148 @@ +setList = $this->phpFlickr->photosets_getList( $userId ); + return $this->setList; + } + + function get_set_list( $userId = null, $before = '', $inner = '', $after = '' ) { + $p = new Profiler(); + $setList = $this->get_user_sets( $userId ); + foreach( $setList['photoset'] as $photoset ) { + if ( !FLOGR_PHOTOSETS_INCLUDE || stristr(FLOGR_PHOTOSETS_INCLUDE, $photoset['title']) ) { + $photosetList .= $this->get_set_link($photoset, $this->get_primary_photo_img($photoset, "square")); + } + } + return $photosetList; + } + + function set_list( $userId = null, $before = '', $after = '' ) { + $p = new Profiler(); + echo $this->get_set_list( $userId, $before, $after ); + } + + function get_set_photos( + $photosetId = null, + $extras = null, + $privacyFilter = null, + $perPage = null, + $page = null) { + $p = new Profiler(); + $photosetId = $photosetId ? $photosetId : $this->paramSetId; + + return $this->phpFlickr->photosets_getPhotos( + $photosetId, + $extras ? $extras : 'original_format,date_taken,date_upload', + $privacyFliter, + $perPage ? $perPage : $this->paramPerPage, + $page ? $page : $this->paramPage); + } + + function set_photos( + $photosetId = null, + $extras = null, + $privacyFilter = null, + $perPage = null, + $page = null) { + $p = new Profiler(); + $photos = $this->get_set_photos($photosetId, $extras, $privacyFilter, $perPage, $page); + $this->slideshow_thumbnails( $photos ); + } + + function get_info( $id = null ) { + $p = new Profiler(); + $id = $id ? $id : $this->paramSetId; + if ( !$setInfo || $setInfo['id'] != $id ) { + $this->setInfo = $this->phpFlickr->photosets_getInfo( $id ); + } + return $this->setInfo; + } + + function get_primary_photo_id( $id = null ) { + $p = new Profiler(); + + $info = $this->getInfo( $id ); + return $info['primary']; + } + + function get_primary_photo_img( $set, $size = null ) { + $p = new Profiler(); + + $sizes = array( + "square" => "_s", + "thumbnail" => "_t", + "small" => "_m", + "medium" => "", + "large" => "_b", + "original" => "_o" + ); + + $size = strtolower($size); + if (!array_key_exists($size, $sizes)) { + $size = "medium"; + } + + $title = htmlspecialchars($set['title'], ENT_QUOTES); + $desc = htmlspecialchars($set['description'], ENT_QUOTES); + + return ""; + } + + function get_photo_count( $id = null ) { + $p = new Profiler(); + + $info = $this->get_info( $id ); + return $info['photos']; + } + + function get_title( $id = null ) { + $p = new Profiler(); + + $info = $this->get_info( $id ); + return $info['title']; + } + + function title( $id = null ) { + $p = new Profiler(); + echo $this->get_title( $id ); + } + + function get_description( $id = null ) { + $p = new Profiler(); + + $info = $this->get_info( $id ); + return $info['description']; + } + + function description( $id = null ) { + $p = new Profiler(); + echo $this->get_description( $id ); + } + + function get_set_link( $set, $inner = '' ) { + $p = new Profiler(); + $title = htmlspecialchars($set['title'], ENT_QUOTES); + $inner = $inner ? $inner : $title; + $desc = htmlspecialchars($set['description'], ENT_QUOTES); + $id = $set['id']; + $url = $this->phpFlickr->buildPhotoURL($set['id'], "Square"); + $string = "{$inner}"; + + return $string; + } + + function set_link( $photosetId = '', $inner = '' ) { + $p = new Profiler(); + echo $this->get_set_link( $photosetId, $inner ); + } +} + +$set = new Flogr_Set(); +?> diff --git a/pages/tags.php b/pages/tags.php new file mode 100644 index 0000000..aff289b --- /dev/null +++ b/pages/tags.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/pages/user.php b/pages/user.php new file mode 100644 index 0000000..7e2a595 --- /dev/null +++ b/pages/user.php @@ -0,0 +1,310 @@ +info || $this->info['nsid'] != $userId) { + $this->info = $this->phpFlickr->people_getInfo( $userId ); + } + return $this->info; + } + + function get_username( $userId = null ) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + return $info['username']; + } + + function username( $userId = null ) { + $p = new Profiler(); + echo $this->get_username( $userId ); + } + + function get_userlink( $userId = null ) { + $p = new Profiler(); + $userId = $userId ? $userId : FLICKR_USER_ID; + return "http://www.flickr.com/people/{$userId}/"; + } + + function userlink( $userId = null, $inner = null ) { + $p = new Profiler(); + $userId = $userId ? $userId : FLICKR_USER_ID; + $inner = $inner ? $inner : $this->get_username($userId); + echo "{$inner}"; + } + + function get_realname( $userId = null ) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + return $info['realname']; + } + + function realname( $userId = null ) { + $p = new Profiler(); + echo $this->get_realname( $userId ); + } + + function get_location( $userId = null ) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + return $info['location']; + } + + function location( $userId = null ) { + $p = new Profiler(); + echo $this->get_location( $userId ); + } + + function get_photoslink( $userId = null, $inner = null) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + $url = $info['photosurl']; + $inner = $inner ? $inner : $url; + + return "{$inner}"; + } + + function photoslink( $userId = null, $inner = null ) { + $p = new Profiler(); + echo $this->get_photoslink( $userId, $inner ); + } + + function get_profilelink( $userId = null, $inner = null ) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + $url = $info['profileurl']; + $inner = $inner ? $inner : $url; + + return "{$inner}"; + } + + function profilelink( $userId = null, $inner = null ) { + $p = new Profiler(); + echo $this->get_profilelink( $userId, $inner ); + } + + function get_mobilelink( $userId = null, $inner = null ) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + $url = $info['mobileurl']; + $inner = $inner ? $inner : $url; + + return "{$inner}"; + } + + function mobilelink( $userId = null, $inner = null ) { + $p = new Profiler(); + echo $this->get_mobilelink( $userId, $inner ); + } + + function get_firstdate( $userId = null, $format = null ) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + $format = $format ? $format : FLOGR_DATE_FORMAT; + return date( $format, $info['photos']['firstdate'] ); + } + + function firstdate( $userId = null, $format = null ) { + $p = new Profiler(); + echo $this->get_firstdate( $userId, $format ); + } + + function get_photos_firstdatetaken( $userId = null, $format = null ) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + $format = $format ? $format : FLOGR_DATE_FORMAT; + return date( $format, strtotime($info['photos']['firstdatetaken']) ); + } + + function photos_firstdatetaken( $userId = null, $format = null ) { + $p = new Profiler(); + echo $this->get_photos_firstdatetaken( $userId, $format ); + } + + function get_photos_count( $userId = null ) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + return $info['photos']['count']; + } + + function photos_count( $userId = null ) { + $p = new Profiler(); + echo $this->get_photos_count( $userId ); + } + + function get_isadmin( $userId = null, $truetext = 'Y', $falsetext = 'N' ) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + return $info['isadmin'] ? $truetext : $falsetext; + } + + function isadmin( $userId = null, $truetext = 'Y', $falsetext = 'N' ) { + $p = new Profiler(); + echo $this->get_isadmin( $userId, $truetext, $falsetext ); + } + + function get_ispro( $userId = null, $truetext = 'Y', $falsetext = 'N' ) { + $p = new Profiler(); + $info = $this->get_info( $userId ); + return $info['ispro'] ? $truetext : $falsetext; + } + + function ispro( $userId = null, $truetext = 'Y', $falestext = 'N' ) { + $p = new Profiler(); + echo $this->get_ispro( $userId, $truetext, $falsetext ); + } + + function get_buddyicon( $userId = null ) { + $p = new Profiler(); + if (!$userId) $userId = FLICKR_USER_ID; + + $info = $this->get_info( $userId ); + $iconserver = $info['iconserver']; + $iconfarm = $info['iconfarm']; + $iconsrc = ''; + + if ( 0 < $iconserver ) { + $iconsrc = "http://farm{$iconfarm}.static.flickr.com/{$iconserver}/buddyicons/{$userId}.jpg"; + } else { + $iconsrc = "http://www.flickr.com/images/buddyicon.jpg"; + } + + return ""; + } + + function buddyicon( $userId = null ) { + $p = new Profiler(); + echo $this->get_buddyicon( $userId ); + } + + function get_blog_list($before = '
  • ', $after = '
  • ') { + $p = new Profiler(); + $blogs = $this->phpFlickr->blogs_getList(); + $blogList = null; + if (!$blogs) return; + + foreach( $blogs['blog'] as $blog ) { + $blogList .= "{$before}" . + $blog['name'] . "{$after}"; + } + return $blogList; + } + + function blog_list($before = null, $after = null) { + $p = new Profiler(); + echo $this->get_blog_list($before, $after); + } + + function get_tag_list($userId = null, $before = '
  • ', $after = '
  • ') { + $p = new Profiler(); + $userId = $userId ? $userId : FLICKR_USER_ID; + $tags = $this->phpFlickr->tags_getListUser( $userId ); + $tagList = null; + if (!$tags) return; + + foreach( $tags as $tag ) { + $tagList .= "{$before}" . + $tag . "{$after}"; + } + return $tagList; + } + + function tag_list($userId = null, $before = '
  • ', $after = '
  • ') { + $p = new Profiler(); + echo $this->get_tag_list($userId, $before, $after); + } + + function get_tag_list_popular($userId = null, $before = '
  • ', $after = '
  • ') { + $p = new Profiler(); + $userId = $userId ? $userId : FLICKR_USER_ID; + $tags = $this->phpFlickr->tags_getListUserPopular( $userId, FLOGR_TAGS_COUNT ); + $tagList = null; + $tagClass = 0; + if (!$tags) return; + + // + // Find the maximum occurence of the 100 most frequently used tag + // + $tagTotal = 0; + foreach ($tags as $tag) + { + $tagTotal += $tag['count']; + } + + // + // Build the tag cloud by wrapping the tag name into a span styled: tag_xsmall, + // tag_small, tag_medium, tag_large, tag_xlarge. + // + $tag_xsmall = ($tagTotal / count($tags)) / 2; + $tag_small = $tag_xsmall * 2; + $tag_medium = $tag_xsmall * 3; + $tag_large = $tag_xsmall * 4; + + foreach ($tags as $tag) + { + if ( FLOGR_TAGS_INCLUDE_HIDE && stristr(FLOGR_TAGS_INCLUDE, $tag['_content']) ) + { + continue; + } + + $tagList .= "{$before}"; + + if (0 < $tag['count'] && $tag['count'] <= $tag_xsmall) + { + $tagList .= "" . $tag['_content'] . " "; + } + else if ($tag_xsmall < $tag['count'] && $tag['count'] <= $tag_small) + { + $tagList .= "" . $tag['_content'] . " "; + } + if ($tag_small < $tag['count'] && $tag['count'] <= $tag_medium) + { + $tagList .= "" . $tag['_content'] . " "; + } + if ($tag_medium < $tag['count'] && $tag['count'] <= $tag_large) + { + $tagList .= "" . $tag['_content'] . " "; + } + if ($tag_large < $tag['count']) + { + $tagList .= "" . $tag['_content'] . " "; + } + + $tagList .= "{$after}"; + } + + return $tagList; + } + + function tag_list_popular($userId = null, $before = '
  • ', $after = '
  • ') { + $p = new Profiler(); + echo $this->get_tag_list_popular($userId, $before, $after); + } + + function get_public_groups($userId = null) { + $p = new Profiler(); + $userId = $userId ? $userId : FLICKR_USER_ID; + return $this->phpFlickr->people_getPublicGroups( $userId ); + } + + function public_groups_list_links( $userId = null, $before = '
  • ', $after = '
  • ' ) { + $p = new Profiler(); + $groups = $this->get_public_groups($userId, $before, $after); + $groupList = ''; + foreach ( $groups as $group ) { + $groupList .= "{$before}{$group['name']}{$after}"; + } + echo $groupList; + } + +} + +$user = new Flogr_User(); +?> \ No newline at end of file diff --git a/themes/blackstripe/about.php b/themes/blackstripe/about.php new file mode 100644 index 0000000..21dceb2 --- /dev/null +++ b/themes/blackstripe/about.php @@ -0,0 +1,64 @@ + + + +
    + +
    + + +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    User

    realname(); ?>

    Location

    location(); ?>

    Profile URL

    profilelink(); ?>

    Photos URL

    photoslink(); ?>

    Mobile URL

    mobilelink(null, $user->get_mobilelink()); ?>

    First Photograph

    firstdate(); ?>

    Photographs Taken

    photos_count(); ?>

    Groups

    + public_groups_list_links(null, '', '

    '); ?> +

    +
    +
    +
    + + \ No newline at end of file diff --git a/themes/blackstripe/css/background.gif b/themes/blackstripe/css/background.gif new file mode 100644 index 0000000..6798426 Binary files /dev/null and b/themes/blackstripe/css/background.gif differ diff --git a/themes/blackstripe/css/blackstripe.css b/themes/blackstripe/css/blackstripe.css new file mode 100644 index 0000000..790434f --- /dev/null +++ b/themes/blackstripe/css/blackstripe.css @@ -0,0 +1,412 @@ +/**************************************** + * Global + ****************************************/ +a img { + color: #fff; + border: 0px; +} + +a:link,a:active,a:visited { + color: #aaa; + text-decoration: none; + text-transform: lowercase; +} + +a:hover { + color: #fff; + border-bottom: 2px solid; +} + +h2 { + color: #777; +} + +h3 { + font-size: 1.2em; + color: #aaa; + margin-top: 0; + padding-right: 10px; +} + +html,body { + margin: 0px; + padding: 0px; + background: url(background.gif) repeat; + color: #cfcfcf; + font-size: 11px; + font-family: "gill sans", verdana, sans-serif; +} + +tr { + vertical-align: top; + text-align: left; +} + +.no_hover a:hover { + border: 0; +} + +.comment { + +} + +.comment_author { + padding-top: 10px; + font-weight: bold; +} + +.comment_content { + margin-left: 2px; + padding-left: 2px; +} + +.comment_date { + +} + + +/**************************************** + * Photo page + ****************************************/ +.photo { + +} + +.photo #prevLink,.photo #nextLink { + display: block; + position: absolute; + top: 0; + width: 50%; + height: 100%; + outline: none; +} + +.photo #prevLink { + left: 0; +} + +.photo #prevLink:hover { + background: transparent url(prevlabel.gif) no-repeat 0 15%; + border: 0px; +} + +.photo #nextLink { + right: 0; +} + +.photo #nextLink:hover { + background: transparent url(nextlabel.gif) no-repeat 100% 15%; + border: 0px; +} + +/**************************************** + * Tags + ****************************************/ +#tag_container { + background: #444; + width: 640px; + padding: 20px 50px 20px 50px; + text-align: center; + line-height: 3.0em; +} + +.tag_xlarge { + font-size: 3.0em; + color: #FFF; + font-weight: 500; +} + +.tag_large { + font-size: 2.5em; + color: #DDD; + font-weight: 400; +} + +.tag_medium { + font-size: 2.0em; + color: #BBB; + font-weight: 300; +} + +.tag_small { + font-size: 1.5em; + color: #999; + font-weight: 200; +} + +.tag_xsmall { + font-size: 1.2em; + color: #777; + font-weight: 100; +} + +/**************************************** + * Thumbnails + ****************************************/ +#thumbnail_container { + background: #444; + width: 730px; + padding: 10px; + text-align: center; +} + +#thumbnail_container a:hover { + border: 0; +} + +#thumbnail_container table { + text-align: left; +} + +.thumbnail { + padding: 2px; + margin: 4px; + background: #fff; + border-right: 2px solid #111; + border-bottom: 2px solid #111; +} + +.thumbnail:hover { + padding: 3px; + margin: 3px; +} + +/**************************************** + * Tool tips + ****************************************/ +.tip { + color: #fff; + width: 250px; + z-index: 1300; + opacity: .9; +} + +.tip-title { + font-weight: bold; + font-size: 11px; + margin: 0; + padding: 8px 8px 4px; + background: #222; +} + +.tip-text { + font-size: 11px; + padding: 4px 8px 8px; + background: #333; +} + +#centercontent { + float: left; + font-size: 1em; + background: #1e1e1e; + padding-bottom: 5px; +} + +#comment_description { + padding-top: 10px; + padding-bottom: 10px; +} + +#comment_thumb { + background: #fff; + padding: 1px; + float: right; + margin-top: 30px; + margin-left: 10px; +} + +#comment_title { + font-size: 1.5em; + border-bottom: 1px solid; + text-transform: lowercase; +} + +/** **/ +#comment_wrapper { + width: 340px; + padding: 5px; +} + +/**************************************** + * Layout + ****************************************/ +#container { + position: relative; + margin: 5px; +} + +#footer { + width: 98%; + height: 15px; + padding: 4px 1% 0px 1%; + text-align: center; + color: #666; + font-size: .9em; + float: left; + margin-top: 10px; +} + +#footer a:hover { + color: #fff; +} + +#header { + float: right; + font-size: 1.0em; + background: #1e1e1e; + padding: 5px; +} + +#header_menu { + float: right; + font-size: 1.0em; + padding: 0px 0px 3px 0px; +} + +#header_menu li { + padding: 0px 0px 0px 3px; + display: inline; +} + +#header_menu .selected { + border-bottom: 2px solid; + padding-bottom: 2px; +} + +/**************************************** + * Header + ****************************************/ +#header_title { + float: left; +} + +/**************************************** + * Comments + ****************************************/ +#info_comments { + position: absolute; + z-index: 99; + background: #1e1e1e; + overflow: auto; + text-align: left; + top: 15px; + left: 15px; + right: 10px; + bottom: 15px; + padding: 15px; + opacity: .85; + filter: alpha(opacity=85); + border: 10px solid #333; + visibility: hidden; +} + + + +#info_comments .clear { + clear: both; + position: relative; +} + + +#info_comments .tags li { + list-style-type: none; + display: inline; + padding-right: 4px; +} + +#info_comments li { + list-style-type: none; + padding-bottom: 15px; +} + +.close { + position: absolute; + top: 2px; + right: 2px; + display: block; +} + +#info_comments .heading { + font-size: 1.6em; + border-bottom: 1px solid; + margin-bottom: 10px; + padding-top: 10px; +} + +#info_comments .thumb img { + border: 2px solid; + margin-bottom: 15px; + margin-right: 10px; + float: left; +} + +#info_comments tr.alt { + background: #ddd; +} + +#comment_form { + display: none; +} + +#leftcontent { + float: left; +} + +#page { + background: #fff; + padding: 15px; + width: 745px; + text-align: center; + position: relative; + clear: both; +} + +/**************************************** + * Pages + ****************************************/ +#page_header { + background: #333; + padding: 10px; + width: 755px; +} + +#page_meta,#page_meta a { + text-align: center; + clear: both; + white-space: nowrap; + width: 765px; + padding: 5px; +} + +#page_nav { + padding: 5px; + float: right; +} + +#page_title { + font-size: 2.0em; + text-transform: lowercase; + font-family: Verdana, sans-serif; +} + +#page2 { + background: #444; + padding: 15px; + width: 745px; + text-align: center; +} + +#rightcontent { + float: right; +} + +#wrapper { + text-align: left; + margin: 0px auto -15px; + width: 775px; + padding: 15px; +} + +#wrapper,#photos { + padding: 15px; +} \ No newline at end of file diff --git a/themes/blackstripe/css/closelabel.gif b/themes/blackstripe/css/closelabel.gif new file mode 100644 index 0000000..af0cab2 Binary files /dev/null and b/themes/blackstripe/css/closelabel.gif differ diff --git a/themes/blackstripe/css/loading.gif b/themes/blackstripe/css/loading.gif new file mode 100644 index 0000000..6371884 Binary files /dev/null and b/themes/blackstripe/css/loading.gif differ diff --git a/themes/blackstripe/css/nextlabel.gif b/themes/blackstripe/css/nextlabel.gif new file mode 100644 index 0000000..7c66121 Binary files /dev/null and b/themes/blackstripe/css/nextlabel.gif differ diff --git a/themes/blackstripe/css/prevlabel.gif b/themes/blackstripe/css/prevlabel.gif new file mode 100644 index 0000000..0641876 Binary files /dev/null and b/themes/blackstripe/css/prevlabel.gif differ diff --git a/themes/blackstripe/css/slimbox2.css b/themes/blackstripe/css/slimbox2.css new file mode 100644 index 0000000..48652a8 --- /dev/null +++ b/themes/blackstripe/css/slimbox2.css @@ -0,0 +1,89 @@ +/* SLIMBOX */ + +#lbOverlay { + position: fixed; + z-index: 9999; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: #000; + cursor: pointer; +} + +#lbCenter, #lbBottomContainer { + position: absolute; + z-index: 9999; + overflow: hidden; + background-color: #fff; +} + +.lbLoading { + background: #fff url(loading.gif) no-repeat center; +} + +#lbImage { + position: absolute; + left: 0; + top: 0; + border: 10px solid #fff; + background-repeat: no-repeat; +} + +#lbPrevLink, #lbNextLink { + display: block; + position: absolute; + top: 0; + width: 50%; + outline: none; +} + +#lbPrevLink { + left: 0; +} + +#lbPrevLink:hover { + background: transparent url(prevlabel.gif) no-repeat 0 15%; +} + +#lbNextLink { + right: 0; +} + +#lbNextLink:hover { + background: transparent url(nextlabel.gif) no-repeat 100% 15%; +} + +#lbBottom { + font-family: Verdana, Arial, Geneva, Helvetica, sans-serif; + font-size: 10px; + color: #666; + line-height: 1.4em; + text-align: left; + border: 10px solid #fff; + border-top-style: none; +} + +#lbCloseLink { + display: block; + float: right; + width: 66px; + height: 22px; + background: transparent url(closelabel.gif) no-repeat center; + margin: 5px 0; + outline: none; +} + +#lbCaption, #lbNumber { + margin-right: 71px; +} + +#lbCaption { + padding: 0px 2px 2px 0px; +} + +#lbCaption a { + font-weight: bold; + color: #777; + float: left; +} \ No newline at end of file diff --git a/themes/blackstripe/favorites.php b/themes/blackstripe/favorites.php new file mode 100644 index 0000000..1e5a17e --- /dev/null +++ b/themes/blackstripe/favorites.php @@ -0,0 +1,34 @@ + +
    + +
    + + +
    + + +
    + slideshow_thumbnails( $photos ); + ?> +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/blackstripe/footer.php b/themes/blackstripe/footer.php new file mode 100644 index 0000000..5ab8f9f --- /dev/null +++ b/themes/blackstripe/footer.php @@ -0,0 +1,2 @@ + + diff --git a/themes/blackstripe/header.php b/themes/blackstripe/header.php new file mode 100644 index 0000000..37a7883 --- /dev/null +++ b/themes/blackstripe/header.php @@ -0,0 +1,14 @@ + + + + + + +
    + +
    + + \ No newline at end of file diff --git a/themes/blackstripe/js/jquery-1.3.2.min.js b/themes/blackstripe/js/jquery-1.3.2.min.js new file mode 100644 index 0000000..b1ae21d --- /dev/null +++ b/themes/blackstripe/js/jquery-1.3.2.min.js @@ -0,0 +1,19 @@ +/* + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
    "]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
    ","
    "]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

    ";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
    ";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
    ").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
    ';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); \ No newline at end of file diff --git a/themes/blackstripe/js/slimbox2.js b/themes/blackstripe/js/slimbox2.js new file mode 100644 index 0000000..d7f2355 --- /dev/null +++ b/themes/blackstripe/js/slimbox2.js @@ -0,0 +1,209 @@ +/* + Slimbox v2.03 - The ultimate lightweight Lightbox clone for jQuery + (c) 2007-2009 Christophe Beyls + MIT-style license. +*/ +(function(w){ + var E=w(window),u,g,F=-1,o,x,D,v,y,L,s,n=!window.XMLHttpRequest,e=window.opera&&(document.compatMode=="CSS1Compat")&&(w.browser.version>=9.3),m=document.documentElement,l={},t=new Image(),J=new Image(),H,a,h,q,I,d,G,c,A,K; + w(function(){ + w("body").append(w([H=w('
    ')[0],a=w('
    ')[0],G=w('
    ')[0]]).css("display","none")); + h=w('
    ').appendTo(a).append(q=w('
    ').append([I=w('').click(B)[0],d=w('').click(f)[0]])[0])[0]; + c=w('
    ').appendTo(G).append([w('').add(H).click(C)[0],A=w('
    ')[0],K=w('
    ')[0],w('
    ')[0]])[0] + }); + w.slimbox=function(O,N,M){ + u=w.extend({ + loop:false, + overlayOpacity:0.8, + overlayFadeDuration:400, + resizeDuration:400, + resizeEasing:"swing", + initialWidth:250, + initialHeight:250, + imageFadeDuration:400, + captionAnimationDuration:400, + counterText:"Image {x} of {y}", + closeKeys:[27,88,67], + previousKeys:[37,80], + nextKeys:[39,78] + },M); + if(typeof O=="string"){ + O=[[O,N]]; + N=0 + } + y=E.scrollTop()+((e?m.clientHeight:E.height())/2); + L=u.initialWidth; + s=u.initialHeight; + w(a).css({ + top:Math.max(0,y-(s/2)), + width:L, + height:s, + marginLeft:-L/2 + }).show(); + v=n||(H.currentStyle&&(H.currentStyle.position!="fixed")); + if(v){ + H.style.position="absolute" + } + w(H).css("opacity",u.overlayOpacity).fadeIn(u.overlayFadeDuration); + z(); + k(1); + g=O; + u.loop=u.loop&&(g.length>1); + return b(N) + }; + + w.fn.slimbox=function(M,P,O){ + P=P||function(Q){ + return[Q.href,Q.rev] + }; + + O=O||function(){ + return true + }; + + var N=this; + return N.unbind("click").click(function(){ + var S=this,U=0,T,Q=0,R; + T=w.grep(N,function(W,V){ + return O.call(S,W,V) + }); + for(R=T.length;Q=0)?C():(M(N,u.nextKeys)>=0)?f():(M(N,u.previousKeys)>=0)?B():false + } + function B(){ + return b(x) + } + function f(){ + return b(D) + } + function b(M){ + if(M>=0){ + F=M; + o=g[F][0]; + x=(F||(u.loop?g.length:0))-1; + D=((F+1)%g.length)||(u.loop?0:-1); + r(); + a.className="lbLoading"; + l=new Image(); + l.onload=j; + l.src=o + } + return false + } + function j(){ + a.className=""; + w(h).css({ + backgroundImage:"url("+o+")", + visibility:"hidden", + display:"" + }); + w(q).width(l.width); + w([q,I,d]).height(l.height); + w(A).html(g[F][1]||""); + w(K).html((((g.length>1)&&u.counterText)||"").replace(/{x}/,F+1).replace(/{y}/,g.length)); + if(x>=0){ + t.src=g[x][0] + } + if(D>=0){ + J.src=g[D][0] + } + L=h.offsetWidth; + s=h.offsetHeight; + var M=Math.max(0,y-(s/2)); + if(a.offsetHeight!=s){ + w(a).animate({ + height:s, + top:M + },u.resizeDuration,u.resizeEasing) + } + if(a.offsetWidth!=L){ + w(a).animate({ + width:L, + marginLeft:-L/2 + },u.resizeDuration,u.resizeEasing) + } + w(a).queue(function(){ + w(G).css({ + width:L, + top:M+s, + marginLeft:-L/2, + visibility:"hidden", + display:"" + }); + w(h).css({ + display:"none", + visibility:"", + opacity:"" + }).fadeIn(u.imageFadeDuration,i) + }) + } + function i(){ + if(x>=0){ + w(I).show() + } + if(D>=0){ + w(d).show() + } + w(c).css("marginTop",-c.offsetHeight).animate({ + marginTop:0 + },u.captionAnimationDuration); + G.style.visibility="" + } + function r(){ + l.onload=null; + l.src=t.src=J.src=o; + w([a,h,c]).stop(true); + w([I,d,h,G]).hide() + } + function C(){ + if(F>=0){ + r(); + F=x=D=-1; + w(a).hide(); + w(H).stop().fadeOut(u.overlayFadeDuration,k) + } + return false + } +})(jQuery); + +// AUTOLOAD CODE BLOCK (MAY BE CHANGED OR REMOVED) +if (!/android|iphone|ipod|series60|symbian|windows ce|blackberry/i.test(navigator.userAgent)) { + jQuery(function($) { + $("a[rel^='lightbox']").slimbox({/* Put custom options here */}, null, function(el) { + return (this == el) || ((this.rel.length > 8) && (this.rel == el.rel)); + }); + }); +} \ No newline at end of file diff --git a/themes/blackstripe/map.php b/themes/blackstripe/map.php new file mode 100644 index 0000000..e0ae728 --- /dev/null +++ b/themes/blackstripe/map.php @@ -0,0 +1,99 @@ + + + + + + +
    + +
    + + +
    + + +
    +
    +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/blackstripe/menu.php b/themes/blackstripe/menu.php new file mode 100644 index 0000000..0a33d82 --- /dev/null +++ b/themes/blackstripe/menu.php @@ -0,0 +1,25 @@ +
    diff --git a/themes/blackstripe/photo.php b/themes/blackstripe/photo.php new file mode 100644 index 0000000..8f546b7 --- /dev/null +++ b/themes/blackstripe/photo.php @@ -0,0 +1,195 @@ + + +
    + +
    + +
    +
    +
    +
    + title(); ?> +
    + +
    +
    +
    + photopage_link(null, $photo->get_img(null, 'Small', 250)); ?> +
    + description(); ?> +
    +
    +

    details

    +
    + get_geo_location(); + if ($geo) { + $lat = $geo["latitude"]; + $long = $geo["longitude"]; + $img = ""; + echo "{$img}"; + } + ?> + + + + + + + + + + + + + + + + + + + + + + get_geo_location(); + if ($geo["latitude"]) { + ?> + + + + + + + + + + + + + +
    Tagstags_list(null, '', ' '); ?>
    Owneruserlink(); ?>
    Datedateposted(); ?>
    Viewsviews_count(); ?>
    Commentscomments_count(); ?>
    Geo + +
    Licenselicense_link(); ?>
    Links + photopage_link(null, "Flickr"); + echo ", "; + $photo->permalink(null, "Permalink"); + ?> +
    +
    +

    exif

    +
    exif(); ?>
    +

    comments

    +
    + comments_list(null, '
  • ', ' says:
    ', '
  • ', false); + $photo->photopage_link(null, "Leave a comment"); + ?> +
    +
    +
    + + + +
    + paramPhotoId) { + $photo->img(null, FLOGR_PHOTO_QUALITY, FLOGR_MAIN_PHOTO_SIZE); + } else { + $photo->next_page_link($photo->photoList, ''); + $photo->previous_page_link($photo->photoList, ''); + $photo->img(null, FLOGR_PHOTO_QUALITY, FLOGR_MAIN_PHOTO_SIZE); + } + ?> +
    + +
    + +
    dateposted(); ?> + + + + + + +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/blackstripe/recent.php b/themes/blackstripe/recent.php new file mode 100644 index 0000000..36cc47e --- /dev/null +++ b/themes/blackstripe/recent.php @@ -0,0 +1,43 @@ + + +
    + +
    + + +
    + + +
    + slideshow_thumbnails( $photos ); + ?> +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/blackstripe/sets.php b/themes/blackstripe/sets.php new file mode 100644 index 0000000..ea084b0 --- /dev/null +++ b/themes/blackstripe/sets.php @@ -0,0 +1,48 @@ + +
    + +
    + + +
    + + +
    + paramSetId ) { + $set->slideshow_thumbnails( $setPhotos ); + } else { + $set->set_list(); + } + ?> +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/blackstripe/tags.php b/themes/blackstripe/tags.php new file mode 100644 index 0000000..7ea1dda --- /dev/null +++ b/themes/blackstripe/tags.php @@ -0,0 +1,29 @@ + + +
    + +
    + + +
    + + +
    + tag_list_popular(null, '', ' +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/whitestripe/about.php b/themes/whitestripe/about.php new file mode 100644 index 0000000..21dceb2 --- /dev/null +++ b/themes/whitestripe/about.php @@ -0,0 +1,64 @@ + + + +
    + +
    + + +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    User

    realname(); ?>

    Location

    location(); ?>

    Profile URL

    profilelink(); ?>

    Photos URL

    photoslink(); ?>

    Mobile URL

    mobilelink(null, $user->get_mobilelink()); ?>

    First Photograph

    firstdate(); ?>

    Photographs Taken

    photos_count(); ?>

    Groups

    + public_groups_list_links(null, '', '

    '); ?> +

    +
    +
    +
    + + \ No newline at end of file diff --git a/themes/whitestripe/css/background.gif b/themes/whitestripe/css/background.gif new file mode 100644 index 0000000..cec4dc7 Binary files /dev/null and b/themes/whitestripe/css/background.gif differ diff --git a/themes/whitestripe/css/closelabel.gif b/themes/whitestripe/css/closelabel.gif new file mode 100644 index 0000000..af0cab2 Binary files /dev/null and b/themes/whitestripe/css/closelabel.gif differ diff --git a/themes/whitestripe/css/loading.gif b/themes/whitestripe/css/loading.gif new file mode 100644 index 0000000..6371884 Binary files /dev/null and b/themes/whitestripe/css/loading.gif differ diff --git a/themes/whitestripe/css/nextlabel.gif b/themes/whitestripe/css/nextlabel.gif new file mode 100644 index 0000000..7c66121 Binary files /dev/null and b/themes/whitestripe/css/nextlabel.gif differ diff --git a/themes/whitestripe/css/prevlabel.gif b/themes/whitestripe/css/prevlabel.gif new file mode 100644 index 0000000..0641876 Binary files /dev/null and b/themes/whitestripe/css/prevlabel.gif differ diff --git a/themes/whitestripe/css/slimbox2.css b/themes/whitestripe/css/slimbox2.css new file mode 100644 index 0000000..48652a8 --- /dev/null +++ b/themes/whitestripe/css/slimbox2.css @@ -0,0 +1,89 @@ +/* SLIMBOX */ + +#lbOverlay { + position: fixed; + z-index: 9999; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: #000; + cursor: pointer; +} + +#lbCenter, #lbBottomContainer { + position: absolute; + z-index: 9999; + overflow: hidden; + background-color: #fff; +} + +.lbLoading { + background: #fff url(loading.gif) no-repeat center; +} + +#lbImage { + position: absolute; + left: 0; + top: 0; + border: 10px solid #fff; + background-repeat: no-repeat; +} + +#lbPrevLink, #lbNextLink { + display: block; + position: absolute; + top: 0; + width: 50%; + outline: none; +} + +#lbPrevLink { + left: 0; +} + +#lbPrevLink:hover { + background: transparent url(prevlabel.gif) no-repeat 0 15%; +} + +#lbNextLink { + right: 0; +} + +#lbNextLink:hover { + background: transparent url(nextlabel.gif) no-repeat 100% 15%; +} + +#lbBottom { + font-family: Verdana, Arial, Geneva, Helvetica, sans-serif; + font-size: 10px; + color: #666; + line-height: 1.4em; + text-align: left; + border: 10px solid #fff; + border-top-style: none; +} + +#lbCloseLink { + display: block; + float: right; + width: 66px; + height: 22px; + background: transparent url(closelabel.gif) no-repeat center; + margin: 5px 0; + outline: none; +} + +#lbCaption, #lbNumber { + margin-right: 71px; +} + +#lbCaption { + padding: 0px 2px 2px 0px; +} + +#lbCaption a { + font-weight: bold; + color: #777; + float: left; +} \ No newline at end of file diff --git a/themes/whitestripe/css/whitestripe.css b/themes/whitestripe/css/whitestripe.css new file mode 100644 index 0000000..857f6d2 --- /dev/null +++ b/themes/whitestripe/css/whitestripe.css @@ -0,0 +1,412 @@ +/**************************************** + * Global + ****************************************/ +a img { + color: #777; + border: 0px; +} + +a:link,a:active,a:visited { + color: #777; + text-decoration: none; + text-transform: lowercase; +} + +a:hover { + color: #777; + border-bottom: 2px solid; +} + +h2 { + color: #777; +} + +h3 { + font-size: 1.2em; + color: #777; + margin-top: 0; + padding-right: 10px; +} + +html,body { + margin: 0px; + padding: 0px; + background: url(background.gif) repeat; + color: #777; + font-size: 11px; + font-family: "gill sans", verdana, sans-serif; +} + +tr { + vertical-align: top; + text-align: left; +} + +.no_hover a:hover { + border: 0; +} + +.comment { + +} + +.comment_author { + padding-top: 10px; + font-weight: bold; +} + +.comment_content { + margin-left: 2px; + padding-left: 2px; +} + +.comment_date { + +} + + +/**************************************** + * Photo page + ****************************************/ +.photo { + +} + +.photo #prevLink,.photo #nextLink { + display: block; + position: absolute; + top: 0; + width: 50%; + height: 100%; + outline: none; +} + +.photo #prevLink { + left: 0; +} + +.photo #prevLink:hover { + background: transparent url(prevlabel.gif) no-repeat 0 15%; + border: 0px; +} + +.photo #nextLink { + right: 0; +} + +.photo #nextLink:hover { + background: transparent url(nextlabel.gif) no-repeat 100% 15%; + border: 0px; +} + +/**************************************** + * Tags + ****************************************/ +#tag_container { + background: #ECECEC; + width: 640px; + padding: 20px 50px 20px 50px; + text-align: center; + line-height: 3.0em; +} + +.tag_xlarge { + font-size: 3.0em; + color: #FFF; + font-weight: 500; +} + +.tag_large { + font-size: 2.5em; + color: #DDD; + font-weight: 400; +} + +.tag_medium { + font-size: 2.0em; + color: #BBB; + font-weight: 300; +} + +.tag_small { + font-size: 1.5em; + color: #999; + font-weight: 200; +} + +.tag_xsmall { + font-size: 1.2em; + color: #777; + font-weight: 100; +} + +/**************************************** + * Thumbnails + ****************************************/ +#thumbnail_container { + background: #ECECEC; + width: 730px; + padding: 10px; + text-align: center; +} + +#thumbnail_container a:hover { + border: 0; +} + +#thumbnail_container table { + text-align: left; +} + +.thumbnail { + padding: 2px; + margin: 4px; + background: #fff; + border-right: 2px solid #999; + border-bottom: 2px solid #999; +} + +.thumbnail:hover { + padding: 3px; + margin: 3px; +} + +/**************************************** + * Tool tips + ****************************************/ +.tip { + color: #fff; + width: 250px; + z-index: 1300; + opacity: .9; +} + +.tip-title { + font-weight: bold; + font-size: 11px; + margin: 0; + padding: 8px 8px 4px; + background: #ECECEC; +} + +.tip-text { + font-size: 11px; + padding: 4px 8px 8px; + background: #ECECEC; +} + +#centercontent { + float: left; + font-size: 1em; + background: #ECECEC; + padding-bottom: 5px; +} + +#comment_description { + padding-top: 10px; + padding-bottom: 10px; +} + +#comment_thumb { + background: #fff; + padding: 1px; + float: right; + margin-top: 30px; + margin-left: 10px; +} + +#comment_title { + font-size: 1.5em; + border-bottom: 1px solid; + text-transform: lowercase; +} + +/** **/ +#comment_wrapper { + width: 340px; + padding: 5px; +} + +/**************************************** + * Layout + ****************************************/ +#container { + position: relative; + margin: 5px; +} + +#footer { + width: 98%; + height: 15px; + padding: 4px 1% 0px 1%; + text-align: center; + color: #777; + font-size: .9em; + float: left; + margin-top: 10px; +} + +#footer a { + color: #444; +} + +#header { + float: right; + font-size: 1.0em; + background: #ECECEC; + padding: 5px; +} + +#header_menu { + float: right; + font-size: 1.0em; + padding: 0px 0px 3px 0px; +} + +#header_menu li { + padding: 0px 0px 0px 3px; + display: inline; +} + +#header_menu .selected { + border-bottom: 2px solid; + padding-bottom: 2px; +} + +/**************************************** + * Header + ****************************************/ +#header_title { + float: left; +} + +/**************************************** + * Comments + ****************************************/ +#info_comments { + position: absolute; + z-index: 99; + background: #ECECEC; + overflow: auto; + text-align: left; + top: 15px; + left: 15px; + right: 10px; + bottom: 15px; + padding: 15px; + opacity: .85; + filter: alpha(opacity=85); + border: 10px solid #ECECEC; + visibility: hidden; +} + + + +#info_comments .clear { + clear: both; + position: relative; +} + + +#info_comments .tags li { + list-style-type: none; + display: inline; + padding-right: 4px; +} + +#info_comments li { + list-style-type: none; + padding-bottom: 15px; +} + +.close { + position: absolute; + top: 2px; + right: 2px; + display: block; +} + +#info_comments .heading { + font-size: 1.6em; + border-bottom: 1px solid; + margin-bottom: 10px; + padding-top: 10px; +} + +#info_comments .thumb img { + border: 2px solid; + margin-bottom: 15px; + margin-right: 10px; + float: left; +} + +#info_comments tr.alt { + background: #ECECEC; +} + +#comment_form { + display: none; +} + +#leftcontent { + float: left; +} + +#page { + background: #fff; + padding: 15px; + width: 745px; + text-align: center; + position: relative; + clear: both; +} + +/**************************************** + * Pages + ****************************************/ +#page_header { + background: #ECECEC; + padding: 10px; + width: 755px; +} + +#page_meta,#page_meta a { + text-align: center; + clear: both; + white-space: nowrap; + width: 765px; + padding: 5px; +} + +#page_nav { + padding: 5px; + float: right; +} + +#page_title { + font-size: 2.0em; + text-transform: lowercase; + font-family: Verdana, sans-serif; +} + +#page2 { + background: #ECECEC; + padding: 15px; + width: 745px; + text-align: center; +} + +#rightcontent { + float: right; +} + +#wrapper { + text-align: left; + margin: 0px auto -15px; + width: 775px; + padding: 15px; +} + +#wrapper,#photos { + padding: 15px; +} \ No newline at end of file diff --git a/themes/whitestripe/favorites.php b/themes/whitestripe/favorites.php new file mode 100644 index 0000000..1e5a17e --- /dev/null +++ b/themes/whitestripe/favorites.php @@ -0,0 +1,34 @@ + +
    + +
    + + +
    + + +
    + slideshow_thumbnails( $photos ); + ?> +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/whitestripe/footer.php b/themes/whitestripe/footer.php new file mode 100644 index 0000000..5ab8f9f --- /dev/null +++ b/themes/whitestripe/footer.php @@ -0,0 +1,2 @@ +
    +
    diff --git a/themes/whitestripe/header.php b/themes/whitestripe/header.php new file mode 100644 index 0000000..5f7e44d --- /dev/null +++ b/themes/whitestripe/header.php @@ -0,0 +1,14 @@ + + + + + + +
    + +
    + + \ No newline at end of file diff --git a/themes/whitestripe/js/jquery-1.3.2.min.js b/themes/whitestripe/js/jquery-1.3.2.min.js new file mode 100644 index 0000000..b1ae21d --- /dev/null +++ b/themes/whitestripe/js/jquery-1.3.2.min.js @@ -0,0 +1,19 @@ +/* + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
    "]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
    ","
    "]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

    ";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
    ";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
    ").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
    ';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); \ No newline at end of file diff --git a/themes/whitestripe/js/slimbox2.js b/themes/whitestripe/js/slimbox2.js new file mode 100644 index 0000000..d7f2355 --- /dev/null +++ b/themes/whitestripe/js/slimbox2.js @@ -0,0 +1,209 @@ +/* + Slimbox v2.03 - The ultimate lightweight Lightbox clone for jQuery + (c) 2007-2009 Christophe Beyls + MIT-style license. +*/ +(function(w){ + var E=w(window),u,g,F=-1,o,x,D,v,y,L,s,n=!window.XMLHttpRequest,e=window.opera&&(document.compatMode=="CSS1Compat")&&(w.browser.version>=9.3),m=document.documentElement,l={},t=new Image(),J=new Image(),H,a,h,q,I,d,G,c,A,K; + w(function(){ + w("body").append(w([H=w('
    ')[0],a=w('
    ')[0],G=w('
    ')[0]]).css("display","none")); + h=w('
    ').appendTo(a).append(q=w('
    ').append([I=w('').click(B)[0],d=w('').click(f)[0]])[0])[0]; + c=w('
    ').appendTo(G).append([w('').add(H).click(C)[0],A=w('
    ')[0],K=w('
    ')[0],w('
    ')[0]])[0] + }); + w.slimbox=function(O,N,M){ + u=w.extend({ + loop:false, + overlayOpacity:0.8, + overlayFadeDuration:400, + resizeDuration:400, + resizeEasing:"swing", + initialWidth:250, + initialHeight:250, + imageFadeDuration:400, + captionAnimationDuration:400, + counterText:"Image {x} of {y}", + closeKeys:[27,88,67], + previousKeys:[37,80], + nextKeys:[39,78] + },M); + if(typeof O=="string"){ + O=[[O,N]]; + N=0 + } + y=E.scrollTop()+((e?m.clientHeight:E.height())/2); + L=u.initialWidth; + s=u.initialHeight; + w(a).css({ + top:Math.max(0,y-(s/2)), + width:L, + height:s, + marginLeft:-L/2 + }).show(); + v=n||(H.currentStyle&&(H.currentStyle.position!="fixed")); + if(v){ + H.style.position="absolute" + } + w(H).css("opacity",u.overlayOpacity).fadeIn(u.overlayFadeDuration); + z(); + k(1); + g=O; + u.loop=u.loop&&(g.length>1); + return b(N) + }; + + w.fn.slimbox=function(M,P,O){ + P=P||function(Q){ + return[Q.href,Q.rev] + }; + + O=O||function(){ + return true + }; + + var N=this; + return N.unbind("click").click(function(){ + var S=this,U=0,T,Q=0,R; + T=w.grep(N,function(W,V){ + return O.call(S,W,V) + }); + for(R=T.length;Q=0)?C():(M(N,u.nextKeys)>=0)?f():(M(N,u.previousKeys)>=0)?B():false + } + function B(){ + return b(x) + } + function f(){ + return b(D) + } + function b(M){ + if(M>=0){ + F=M; + o=g[F][0]; + x=(F||(u.loop?g.length:0))-1; + D=((F+1)%g.length)||(u.loop?0:-1); + r(); + a.className="lbLoading"; + l=new Image(); + l.onload=j; + l.src=o + } + return false + } + function j(){ + a.className=""; + w(h).css({ + backgroundImage:"url("+o+")", + visibility:"hidden", + display:"" + }); + w(q).width(l.width); + w([q,I,d]).height(l.height); + w(A).html(g[F][1]||""); + w(K).html((((g.length>1)&&u.counterText)||"").replace(/{x}/,F+1).replace(/{y}/,g.length)); + if(x>=0){ + t.src=g[x][0] + } + if(D>=0){ + J.src=g[D][0] + } + L=h.offsetWidth; + s=h.offsetHeight; + var M=Math.max(0,y-(s/2)); + if(a.offsetHeight!=s){ + w(a).animate({ + height:s, + top:M + },u.resizeDuration,u.resizeEasing) + } + if(a.offsetWidth!=L){ + w(a).animate({ + width:L, + marginLeft:-L/2 + },u.resizeDuration,u.resizeEasing) + } + w(a).queue(function(){ + w(G).css({ + width:L, + top:M+s, + marginLeft:-L/2, + visibility:"hidden", + display:"" + }); + w(h).css({ + display:"none", + visibility:"", + opacity:"" + }).fadeIn(u.imageFadeDuration,i) + }) + } + function i(){ + if(x>=0){ + w(I).show() + } + if(D>=0){ + w(d).show() + } + w(c).css("marginTop",-c.offsetHeight).animate({ + marginTop:0 + },u.captionAnimationDuration); + G.style.visibility="" + } + function r(){ + l.onload=null; + l.src=t.src=J.src=o; + w([a,h,c]).stop(true); + w([I,d,h,G]).hide() + } + function C(){ + if(F>=0){ + r(); + F=x=D=-1; + w(a).hide(); + w(H).stop().fadeOut(u.overlayFadeDuration,k) + } + return false + } +})(jQuery); + +// AUTOLOAD CODE BLOCK (MAY BE CHANGED OR REMOVED) +if (!/android|iphone|ipod|series60|symbian|windows ce|blackberry/i.test(navigator.userAgent)) { + jQuery(function($) { + $("a[rel^='lightbox']").slimbox({/* Put custom options here */}, null, function(el) { + return (this == el) || ((this.rel.length > 8) && (this.rel == el.rel)); + }); + }); +} \ No newline at end of file diff --git a/themes/whitestripe/map.php b/themes/whitestripe/map.php new file mode 100644 index 0000000..e0ae728 --- /dev/null +++ b/themes/whitestripe/map.php @@ -0,0 +1,99 @@ + + + + + + +
    + +
    + + +
    + + +
    +
    +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/whitestripe/menu.php b/themes/whitestripe/menu.php new file mode 100644 index 0000000..0a33d82 --- /dev/null +++ b/themes/whitestripe/menu.php @@ -0,0 +1,25 @@ +
    diff --git a/themes/whitestripe/photo.php b/themes/whitestripe/photo.php new file mode 100644 index 0000000..8f546b7 --- /dev/null +++ b/themes/whitestripe/photo.php @@ -0,0 +1,195 @@ + + +
    + +
    + +
    +
    +
    +
    + title(); ?> +
    + +
    +
    +
    + photopage_link(null, $photo->get_img(null, 'Small', 250)); ?> +
    + description(); ?> +
    +
    +

    details

    +
    + get_geo_location(); + if ($geo) { + $lat = $geo["latitude"]; + $long = $geo["longitude"]; + $img = ""; + echo "{$img}"; + } + ?> + + + + + + + + + + + + + + + + + + + + + + get_geo_location(); + if ($geo["latitude"]) { + ?> + + + + + + + + + + + + + +
    Tagstags_list(null, '', ' '); ?>
    Owneruserlink(); ?>
    Datedateposted(); ?>
    Viewsviews_count(); ?>
    Commentscomments_count(); ?>
    Geo + +
    Licenselicense_link(); ?>
    Links + photopage_link(null, "Flickr"); + echo ", "; + $photo->permalink(null, "Permalink"); + ?> +
    +
    +

    exif

    +
    exif(); ?>
    +

    comments

    +
    + comments_list(null, '
  • ', ' says:
    ', '
  • ', false); + $photo->photopage_link(null, "Leave a comment"); + ?> +
    +
    +
    + + + +
    + paramPhotoId) { + $photo->img(null, FLOGR_PHOTO_QUALITY, FLOGR_MAIN_PHOTO_SIZE); + } else { + $photo->next_page_link($photo->photoList, ''); + $photo->previous_page_link($photo->photoList, ''); + $photo->img(null, FLOGR_PHOTO_QUALITY, FLOGR_MAIN_PHOTO_SIZE); + } + ?> +
    + +
    + +
    dateposted(); ?> + + + + + + +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/whitestripe/recent.php b/themes/whitestripe/recent.php new file mode 100644 index 0000000..36cc47e --- /dev/null +++ b/themes/whitestripe/recent.php @@ -0,0 +1,43 @@ + + +
    + +
    + + +
    + + +
    + slideshow_thumbnails( $photos ); + ?> +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/whitestripe/sets.php b/themes/whitestripe/sets.php new file mode 100644 index 0000000..ea084b0 --- /dev/null +++ b/themes/whitestripe/sets.php @@ -0,0 +1,48 @@ + +
    + +
    + + +
    + + +
    + paramSetId ) { + $set->slideshow_thumbnails( $setPhotos ); + } else { + $set->set_list(); + } + ?> +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/themes/whitestripe/tags.php b/themes/whitestripe/tags.php new file mode 100644 index 0000000..7ea1dda --- /dev/null +++ b/themes/whitestripe/tags.php @@ -0,0 +1,29 @@ + + +
    + +
    + + +
    + + +
    + tag_list_popular(null, '', ' +
    +
    +
    + +
    + + \ No newline at end of file