<?php
/*
 * $RCSfile: GalleryStatus.class,v $
 *
 * Gallery - a web based photo album viewer and editor
 * Copyright (C) 2000-2005 Bharat Mediratta
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
 */
/**
 * @version $Revision: 1.50 $ $Date: 2005/08/23 03:49:03 $
 * @package GalleryCore
 * @author Bharat Mediratta <bharat@menalto.com>
 */

/*
 * ****************************************
 *           Status code bitflags
 * ****************************************
 */

/**
 * The operation was a success
 */
define('GALLERY_SUCCESS', 0x00000000);

/**
 * The operation had errors
 */
define('GALLERY_ERROR', 0x00000001);

/**
 * A name collision happened in the filesystem or database as a result of this
 * operation.  A common cause for this is attempting to use an existing filename
 * when moving an item from one location to another.
 */
define('ERROR_COLLISION', 0x00000002);

/**
 * The object you're trying to access is no longer available.  Perhaps it was
 * deleted.  You shouldn't get this when an object has simply moved.
 */
define('ERROR_MISSING_OBJECT', 0x00000004);

/**
 * The lock you're trying to acquire is currently in use and was not released
 * within the timeout period you specified.
 */
define('ERROR_LOCK_IN_USE', 0x00000008);

/**
 * One of the parameters passed to this function is bad.
 */
define('ERROR_BAD_PARAMETER', 0x00000010);

/**
 * Missing a value necessary to continue
 */
define('ERROR_MISSING_VALUE', 0x00000020);

/**
 * An unspecified storage error occurred
 */
define('ERROR_STORAGE_FAILURE', 0x00000040);

/**
 * A storage operation was attempted with an invalid storage connection
 */
define('ERROR_STORAGE_CONNECTION', 0x00000080);

/**
 * You attempted to modify an object using an in-memory version that
 * is out of date with the version that's in the storage
 */
define('ERROR_OBSOLETE_DATA', 0x00000200);

/**
 * You attempted an operation which requires a lock.
 */
define('ERROR_LOCK_REQUIRED', 0x00000400);

/**
 * We tried a file operation on an unsupported file type
 */
define('ERROR_UNSUPPORTED_FILE_TYPE', 0x00000800);

/**
 * You attempted an illegal operation on a deleted object
 */
define('ERROR_DELETED_OBJECT', 0x00001000);

/**
 * You attempted an operation which had a bad path component
 */
define('ERROR_BAD_PATH', 0x00002000);

/**
 * You attempted an operation which had a bad data type
 */
define('ERROR_BAD_DATA_TYPE', 0x00004000);

/**
 * You attempted to add a child to a GalleryItem which can't have children
 */
define('ERROR_ILLEGAL_CHILD', 0x00008000);

/**
 * An unspecified error occured while completing a toolkit command.
 */
define('ERROR_TOOLKIT_FAILURE', 0x00010000);

/**
 * We experienced a platform specific error (perhaps filesystem related)
 */
define('ERROR_PLATFORM_FAILURE', 0x00020000);

/**
 * You did an operation on a derivative that is broken
 */
define('ERROR_BROKEN_DERIVATIVE', 0x00040000);

/**
 * We tried an unsupported operation
 */
define('ERROR_UNSUPPORTED_OPERATION', 0x00080000);

/**
 * We were unable to get a lock in the time allotted
 */
define('ERROR_LOCK_TIMEOUT', 0x00100000);

/**
 * Something went wrong when loading or activating a plugin
 */
define('ERROR_BAD_PLUGIN', 0x00200000);

/**
 * The module you tried to use requires configuration
 */
define('ERROR_CONFIGURATION_REQUIRED', 0x00400000);

/**
 * You don't have permission to complete the given action
 */
define('ERROR_PERMISSION_DENIED', 0x00800000);

/**
 * You don't have enough space for the operation required
 */
define('ERROR_OUT_OF_SPACE', 0x01000000);

/**
 * The plugin exists, but the version on disk doesn't match the
 * version in the database.
 */
define('ERROR_PLUGIN_VERSION_MISMATCH', 0x02000000);

/**
 * The operation you attempted is unimplemented
 */
define('ERROR_UNIMPLEMENTED', 0x40000000);

/**
 * An unknown error occurred
 */
define('ERROR_UNKNOWN', 0x80000000);

/**
 * Global storage container and utility class for Gallery
 *
 * This is a container for global information required for gallery
 * operation, such as configuration, session, user, etc.
 *
 * @package GalleryCore
 * @subpackage Classes
 */
class GalleryStatus {

    /*
     * ****************************************
     *                 Members
     * ****************************************
     */

    /**
     * The stack trace, if possible.
     */
    var $_stack;

    /**
     * The actual error code
     *
     * @var string $_errorCode
     * @access private
     */
    var $_errorCode;

    /**
     * The file name where the error occurred
     *
     * @var string $_fileName
     * @access private
     */
    var $_fileName;

    /**
     * The line number where the error occurred
     *
     * @var string $_lineNumber
     * @access private
     */
    var $_lineNumber;

    /**
     * A descriptive message of the error
     *
     * @var string $_errorMessage
     * @access private
     */
    var $_errorMessage;

    /*
     * ****************************************
     *                 Methods
     * ****************************************
     */

    /**
     * Constructor
     * @param errorCode an error code
     * @param fileName the path to the file where the error occurred
     * @param lineNumber the line number in the file where the error occurred
     * @param errorMessage descriptive message of the error
     */
    function GalleryStatus($errorCode, $fileName=null, $lineNumber=null, $errorMessage=null) {
	$this->_errorCode = $errorCode;
	$this->_fileName = array($fileName);
	$this->_lineNumber = array($lineNumber);
	$this->_errorMessage = $errorMessage;
    }

    /**
     * Return an error status
     *
     * @return object GalleryStatus an error status
     * @static
     */
    function error($errorCode, $fileName, $lineNumber, $errorMessage=null) {
	$status = new GalleryStatus(GALLERY_ERROR | $errorCode, $fileName, $lineNumber, $errorMessage);
	if (function_exists('debug_backtrace')) {
	    $status->setStackTrace(debug_backtrace());
	}
	return $status;
    }

    /**
     * Return a success status
     *
     * @return object GalleryStatus a successful status
     * @staticvar GalleryStatus $success An object stating that everything went well
     * @static
     */
    function success() {

	/*
	 * Use a static singleton for the success object, so that we don't have
	 * to construct new objects needlessly.
	 */
	static $success;
	if (empty($success)) {
	    $success = new GalleryStatus(GALLERY_SUCCESS);
	}
	return $success;
    }

    /**
     * Set the stack trace
     *
     * @param array (array, array)
     */
    function setStackTrace($trace) {
	$this->_stackTrace = $trace;
    }

    /**
     * Return the filename the error was found in.
     *
     * @return string
     */
    function getFileName() {
	return $this->_fileName[0];
    }

    /**
     * Return the line number the error was found at.
     *
     * @return int
     */
    function getLineNumber() {
	return $this->_lineNumber[0];
    }

    /**
     * Return the actual error code
     *
     * @return int
     */
    function getErrorCode() {
	return $this->_errorCode;
    }

    /**
     * Return the error message
     *
     * @return string
     */
    function getErrorMessage() {
	return $this->_errorMessage;
    }

    /**
     * Add a new code to our set of codes
     *
     * @param int an error code
     */
    function addErrorCode($code) {
	$this->_errorCode |= $code;
    }

    /**
     * Add a new file name and line number to our stack trace
     *
     * Don't add the value, unless it's an error.  As a debug measure, whine if
     * somebody tries to add a trace to a success code, because the success
     * code is supposed to be a singleton.
     *
     * @param string a file name
     * @param int a line number
     * @return object GalleryStatus the new status object
     */
    function wrap($fileName, $lineNumber) {
	if (!empty($this->_errorCode)) {
	    array_push($this->_fileName, $fileName);
	    array_push($this->_lineNumber, $lineNumber);
	}

	return $this;
    }

    /**
     * Is this an error?
     *
     * @return boolean
     */
    function isError() {
	return $this->_errorCode & GALLERY_ERROR;
    }

    /**
     * Is this a success?
     *
     * @return boolean
     */
    function isSuccess() {
	return $this->_errorCode == GALLERY_SUCCESS;
    }

    /**
     * Return the error as an HTML string
     *
     * @param boolean (optional) false to omit errorMessage
     * @return string
     */
    function getAsHtml($showMessage=true) {
	list ($codes, $trace) = $this->_getAsArray();

	$message = $showMessage ? $this->_errorMessage : '';
	$buf = 'Error  (' . join(', ', $codes) . ')';
	if (!is_null($message)) {
	    $buf .= ' : ' . htmlentities($message);
	}
	$buf .= '<ul>';
	foreach ($trace as $traceEntry) {
	    $buf .= sprintf("<li><b>in</b> %s <b>at line</b> %d",
			    $traceEntry['file'], $traceEntry['line']);
	    if (isset($traceEntry['class']) && isset($traceEntry['function'])) {
		$buf .= " ($traceEntry[class]::$traceEntry[function]) ";
	    } else if (isset($traceEntry['class'])) {
		$buf .= " ($traceEntry[function]) ";
	    }
	}
	$buf .= '</ul>';

	return $buf;
    }

    /**
     * Return the error as a plain text string delimited by newlines
     *
     * @param boolean (optional) false to omit errorMessage
     * @return string
     */
    function getAsText($showMessage=true) {
	list ($codes, $trace) = $this->_getAsArray();

	$message = $showMessage ? $this->_errorMessage : '';
	$buf = 'Error (' . join(', ', $codes) . ')';
	if (!is_null($message)) {
	    $buf .= ' : ' . htmlentities($message);
	}
	foreach ($trace as $traceEntry) {
	    $buf .= sprintf("<b>in</b> %s <b>at line</b> %d",
			    $traceEntry['file'], $traceEntry['line']);
	    if (isset($traceEntry['class']) && isset($traceEntry['function'])) {
		$buf .= " ($traceEntry[class]::$traceEntry[function]) ";
	    } else if (isset($traceEntry['class'])) {
		$buf .= " ($traceEntry[function]) ";
	    }
	    $buf .= "\n";
	}
	return $buf;
    }

    /**
     * Break down an error code into a list of constants
     * @return array of strings
     */
    function getErrorCodeConstants($errorCode) {
	if ($errorCode == 0) {
	    $codes = array('GALLERY_SUCCESS');
	} else {
	    $codes = array();
	    /* get_defined_constants() arrived in 4.1.0 */
	    if (function_exists('get_defined_constants')) {
		foreach (get_defined_constants() as $constantName => $constantValue) {
		    if (strpos($constantName, 'ERROR_') === 0) {
			if ($errorCode & $constantValue) {
			    $codes[] = $constantName;
			}
		    }
		}
		if (empty($codes)) {
		    /* No specific error specified */
		    $codes = array('GALLERY_ERROR');
		}
	    } else {
		$codes[] = 'UnknownErrorName';
	    }
	}

	return $codes;
    }

    /**
     * Internal function turn error code and stack trace into text
     *
     * @return string
     * @access private
     */
    function _getAsArray() {
	global $gallery;

	$codes = $this->getErrorCodeConstants($this->_errorCode);
	$trace = array();
	if (!class_exists('GalleryTestCase')) {
	    $platform = $gallery->getPlatform();
	    $base = $platform->realpath(dirname(__FILE__) . '/../../../') . '/';
	} else {
	    $base = '';
	}
	if (empty($this->_stackTrace)) {
	    for ($i = 0; $i < count($this->_fileName); $i++) {
		$trace[] = array('file' => str_replace($base, '', $this->_fileName[$i]),
				 'line' => $this->_lineNumber[$i],
				 'class' => null,
				 'function' => null);
	    }
	} else {
	    foreach ($this->_stackTrace as $traceEntry) {
		if (empty($traceEntry['file'])) {
		    $traceEntry['file'] = '???';
		}
		if (empty($traceEntry['line'])) {
		    $traceEntry['line'] = '???';
		}
		$trace[] =
		    array('file' => str_replace($base, '', $traceEntry['file']),
			  'line' => $traceEntry['line'],
			  'class' => empty($traceEntry['class']) ? null : $traceEntry['class'],
			  'function' => empty($traceEntry['function']) ?
			  null : $traceEntry['function']);
	    }
	}

	return array($codes, $trace);
    }
}
?>
