<?php
/*
 * $RCSfile: GalleryPlugin.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.18 $ $Date: 2005/08/23 03:49:03 $
 * @package GalleryCore
 * @author Bharat Mediratta <bharat@menalto.com>
 */

/**
 * Plugin meta-info container
 *
 * This is a container for information about a given plugin.
 *
 * @package GalleryCore
 * @subpackage Classes
 */
class GalleryPlugin {

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

    /**
     * The id of this plugin
     *
     * @var string _id
     * @access private
     */
    var $_id;

    /**
     * The name of this plugin
     *
     * @var string _name
     * @access private
     */
    var $_name;

    /**
     * The description of this plugin
     *
     * @var string _description
     * @access private
     */
    var $_description;

    /**
     * The version of this plugin
     *
     * @var string _version
     * @access private
     */
    var $_version;

    /**
     * The version of the Core API required by this plugin
     *
     * @var array _requiredCoreApi
     * @access private
     */
    var $_requiredCoreApi;

    /**
     * The localization domain for this plugin
     *
     * @var string _l10Domain
     * @access private
     */
    var $_l10Domain;

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

    /**
     * Localize the given content.
     *
     * Gallery uses GNU gettext for internationalization (i18n) and localization (l10n) of
     * text presented to the user. Gettext needs to know about all places involving strings,
     * that must be translated. Mark any place, where localization at runtime shall take place
     * by using this function.
     *
     * E.g. instead of:
     *   print 'some text to be spit out in different languages';
     * use (in any modules subclass of GalleryPlugin):
     *   print $this->translate('some text to be spit out in different languages');
     * and you are all set for pure literals. The translation teams will receive that literal
     * string as a job to translate and will translate it (when the message is clear enough).
     * At runtime the message is then printed localized.
     *
     * But consider this case:
     *   $message_to_be_localized = 'You did it right!';
     *   print $this->translate($message_to_be_localized);
     *
     * The translate() method is called in the right place for runtime handling, but there
     * is no message at gettext preprocessing time to be given to the translation teams,
     * just a variable name. Translation of the variable name would break the code! So all
     * places potentially feeding this variable have to be marked to be given to translation
     * teams, but not translated at runtime!
     *
     * The (noop) method Gallery::i18n() is there to resolve all such cases (including storing
     * the original string in, say, the database). Simply mark the candidates:
     *   $message_to_be_localized = $gallery->i18n('You did it right!');
     *   print $this->translate($message_to_be_localized);
     *
     * The i18n() method does nothing, but feeding translators with that new string.
     * This method does the runtime job, thanks to GNU gettext.
     *
     * @param mixed a single string, or an array of parameters
     * @return string the localized value
     * @see Gallery::i18n()
     */
    function translate($params) {
	global $gallery;

	if (!is_array($params)) {
	    $params = array('text' => $params);
	}

	$translator =& $gallery->getTranslator();
	list ($ret, $content) =
	    $translator->translateDomain($this->getPluginType() . 's_' . $this->getId(), $params);
	if ($ret->isError()) {
	    if ($gallery->getDebug()) {
		$gallery->debug($ret->getAsHtml());
	    }
	    return 'Translation error';
	} else {
	    return $content;
	}
    }

    /**
     * Activate this plugin
     *
     * @param bool should we post an activation event?  Normally true, but the
     *             upgrader may choose to suppress this event so that it can
     *             reactivate a module without causing a ripple effect.
     * @return array object GalleryStatus a status code
     *               array redirect info for error page (empty for success)
     */
    function activate($postActivationEvent=true) {
	global $gallery;
	
	$pluginType = $this->getPluginType();
	$pluginId = $this->getId();

	if ($gallery->getDebug()) {
	    $gallery->debug(sprintf('GalleryPlugin::activate %s plugin', $pluginId));
	}
	
	list ($ret, $pluginStatus) = GalleryCoreApi::fetchPluginStatus($pluginType);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	GalleryCoreApi::relativeRequireOnce('modules/core/classes/GalleryPluginMap.class');
	if (isset($pluginStatus[$pluginId]['active'])) {
	    $ret = GalleryPluginMap::updateMapEntry(array('pluginType' => $pluginType,
							  'pluginId' => $pluginId),
						    array('active' => 1));
	} else {
	    $ret = GalleryPluginMap::addMapEntry(array('pluginType' => $pluginType,
						       'pluginId' => $pluginId,
						       'active' => 1));
	}
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	/* Flush the cache */
	GalleryDataCache::remove("GalleryPluginHelper::fetchPluginStatus($pluginType)");
	GalleryDataCache::removeFromDisk(array('type' => $pluginType,
					       'itemId' => 'GalleryPluginHelper_fetchPluginStatus',
					       'id' => '_all'));

	GalleryDataCache::remove("GalleryPluginHelper::fetchPluginList($pluginType)");

	if ($postActivationEvent) {
	    if ($gallery->getDebug()) {
		$gallery->debug('GalleryPlugin::activate post activation event');
	    }
	    
	    $event = GalleryCoreApi::newEvent('Gallery::ActivatePlugin');
	    $event->setData(array('pluginType' => $this->getPluginType(),
				  'pluginId' => $this->getId()));
	    list ($ret) = GalleryCoreApi::postEvent($event);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	}

	if ($gallery->getDebug()) {
	    $gallery->debug(sprintf('GalleryPlugin::activate %s plugin successfully activated',
				    $pluginId));
	}

	return array(GalleryStatus::success(), array());
    }

    /**
     * Deactivate this plugin
     *
     * @param bool should we post a deactivation event?  Normally true, but the
     *             upgrader may choose to suppress this event so that it can
     *             reactivate a module without causing a ripple effect.
     * @return array object GalleryStatus a status code
     *               array redirect info for error page (empty for success)
     */
    function deactivate($postDeactivationEvent=true) {
	$pluginType = $this->getPluginType();
	$pluginId = $this->getId();

	GalleryCoreApi::relativeRequireOnce('modules/core/classes/GalleryPluginMap.class');
	$ret = GalleryPluginMap::updateMapEntry(array('pluginType' => $pluginType,
						      'pluginId' => $pluginId),
						array('active' => 0));
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	/* Flush the cache */
	GalleryDataCache::remove("GalleryPluginHelper::fetchPluginStatus($pluginType)");
	GalleryDataCache::removeFromDisk(array('type' => $pluginType,
					       'itemId' => 'GalleryPluginHelper_fetchPluginStatus',
					       'id' => '_all'));
	GalleryDataCache::remove("GalleryPluginHelper::fetchPluginList($pluginType)");

	if ($postDeactivationEvent) {
	    $ret = $this->_postDeactivationEvent();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	}

	return array(GalleryStatus::success(), array());
    }

    /**
     * Reactivate this plugin.  The plugin is already active, so call deactivate() and then
     * activate().  Perform the deactivation without sending an event because the activation will
     * immediately follow and we don't want other plugins to sense this deactivation and
     * deactivate themselves.  If the plugin isn't currently active, don't do anything.
     *
     * @return array object GalleryStatus a status code
     *               array redirect info for error page (empty for success)
     */
    function reactivate() {
	global $gallery;
	
	if ($gallery->getDebug()) {
	    $gallery->debug(sprintf('GalleryPlugin::reactivate %s plugin', $this->getId()));
	}
	
	list ($ret, $isActive) = $this->isActive();
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	if (!$isActive) {
	    if ($gallery->getDebug()) {
		$gallery->debug(sprintf('GalleryPlugin::reactivate %s plugin, plugin is ' .
					'not active, nothing to do', $this->getId()));
	    }	    
	    return array(GalleryStatus::success(), null);
	}

	if ($gallery->getDebug()) {
	    $gallery->debug(sprintf('GalleryPlugin::reactivate %s plugin, deactivate',
				    $this->getId()));
	}
	
	list ($ret, $redirect) = $this->deactivate(false);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	if (!empty($redirect)) {
	    return array(GalleryStatus::success(), $redirect);
	}

	if ($gallery->getDebug()) {
	    $gallery->debug(sprintf('GalleryPlugin::reactivate %s plugin, activate again',
				    $this->getId()));
	}
	
	list ($ret, $redirect) = $this->activate(false);
	if ($ret->isError()) {
	    /* Try to send the deactivation event before failing completely. */
	    $this->_postDeactivationEvent();
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	if (!empty($redirect)) {
	    /* Try to send the deactivation event before returning the redirect. */
	    $this->_postDeactivationEvent();
	    return array(GalleryStatus::success(), $redirect);
	}

	if ($gallery->getDebug()) {
	    $gallery->debug(sprintf('GalleryPlugin::reactivate %s plugin, successfully ' .
				    'reactivated', $this->getId()));
	}
	
	return array(GalleryStatus::success(), array());
    }

    /**
     * Post a deactivation event for this plugin.  Used by deactivate and reactivate.
     *
     * @return object GalleryStatus a status code
     * @access private
     */
    function _postDeactivationEvent() {
	$event = GalleryCoreApi::newEvent('Gallery::DeactivatePlugin');
	$event->setData(array('pluginType' => $this->getPluginType(),
			      'pluginId' => $this->getId()));
	list ($ret) = GalleryCoreApi::postEvent($event);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	return GalleryStatus::success();
    }

    /**
     * Remove this plugin's parameters
     *
     * @return object GalleryStatus a status code
     */
    function uninstall() {
	global $gallery;

	/* Remove this plugin */
	$ret = GalleryCoreApi::removePlugin($this->getPluginType(), $this->getId());
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	$event = GalleryCoreApi::newEvent('Gallery::UninstallPlugin');
	$event->setData(array('pluginType' => $this->getPluginType(),
			      'pluginId' => $this->getId()));
	list ($ret) = GalleryCoreApi::postEvent($event);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	return GalleryStatus::success();
    }

    /**
     * Perform the plugin installation or upgrade, whatever is required.
     *
     * Plugins should not need to override this method.  Instead they should
     * override the upgrade method and put all their plugin specific logic
     * there.
     *
     * @return object GalleryStatus a status code
     */
    function installOrUpgrade() {
	return GalleryStatus::success();
    }

    /**
     * Is this plugin active?
     *
     * @return array object GalleryStatus a status code
     *               boolean true if active
     */
    function isActive() {
	global $gallery;

	list($ret, $pluginStatus) = GalleryCoreApi::fetchPluginStatus($this->getPluginType());
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	if (empty($pluginStatus[$this->getId()])) {
	    return array(GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
			      sprintf("No such %s: %s", $this->getPluginType(), $this->getId())),
			 null);
	}

	return array(GalleryStatus::success(),
		     empty($pluginStatus[$this->getId()]['active']) ? false : true);
    }

    /**
     * Convenience method to get a plugin parameter
     *
     * @param string the parameter name
     * @param int the (optional) item id
     * @return array object GalleryStatus a status code
     *               mixed value
     */
    function getParameter($parameterName, $itemId=0) {
	list ($ret, $value) = GalleryCoreApi::getPluginParameter(
	    $this->getPluginType(), $this->getId(), $parameterName, $itemId);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	return array(GalleryStatus::success(), $value);
    }

    /**
     * Convenience method to set a plugin parameter
     *
     * @param string the parameter name
     * @param string the value
     * @param int the (optional) item id
     * @return object GalleryStatus a status code
     */
    function setParameter($parameterName, $parameterValue, $itemId=0) {
	$ret = GalleryCoreApi::setPluginParameter(
	    $this->getPluginType(), $this->getId(), $parameterName, $parameterValue, $itemId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	return GalleryStatus::success();
    }

    /**
     * Convenience method to remove a plugin parameter
     *
     * @param string the parameter name
     * @param int the (optional) item id
     * @return object GalleryStatus a status code
     */
    function removeParameter($parameterName, $itemId=0) {
	$ret = GalleryCoreApi::removePluginParameter(
	    $this->getPluginType(), $this->getId(), $parameterName, $itemId);
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	return GalleryStatus::success();
    }

    /**
     * Fetch all plugin specific parameters for the given item.  The results will contain
     * a mixture of global parameters and item specific parameters, where the
     * item specific ones ones override the global ones.
     *
     * @param int optional itemId
     * @return array object GalleryStatus a status code
     *               array parameters in key => value form
     */
    function fetchParameters($itemId=null) {
	$order = array(null);
	if ($itemId) {
	    $order[] = $itemId;
	}

	$results = array();
	/* TODO: we should do this in one database query, ordered by id. */
	foreach ($order as $id) {
	    list ($ret, $params) = GalleryCoreApi::fetchAllPluginParameters(
		$this->getPluginType(), $this->getId(), $id);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    $results = array_merge($results, $params);
	}

	return array(GalleryStatus::success(), $results);
    }

    /**
     * Perform any upgrade tasks required at this point.  This method is called
     * if the plugin version in the code does not match the version number in
     * the database.  For modules, the framework will upgrade database tables as
     * necessary, but it is the responsibility of the module to:
     *
     * 1. Register/unregister permissions
     * 2. Move or massage data as required by the upgrade
     *
     * This method will be called with a null version on an initial install.
     * @param string the current version (null if this is an initial install)
     * @return object GalleryStatus a status code
     * @access protected
     */
    function upgrade($currentVersion) {
	return GalleryStatus::success();
    }

    /* Getters and setters below */

    function setId($id) {
	$this->_id = $id;
    }

    function getId() {
	return $this->_id;
    }

    function setName($name) {
	$this->_name = $name;
    }

    function getName() {
	return $this->_name;
    }

    function setDescription($description) {
	$this->_description = $description;
    }

    function getDescription() {
	return $this->_description;
    }

    function setVersion($version) {
	$this->_version = $version;
    }

    function getVersion() {
	return $this->_version;
    }

    function setGroup($group, $groupLabel) {
	$this->_group = array('group' => $group, 'groupLabel' => $groupLabel);
    }

    function getGroup() {
	return $this->_group;
    }

    function getL10Domain() {
	return $this->getPluginType() . 's_' . $this->getId();
    }

    function setRequiredCoreApi($requirement) {
	$this->_requiredCoreApi = $requirement;
    }

    function getRequiredCoreApi() {
	return $this->_requiredCoreApi;
    }

    /**
     * @access protected
     */
    function getPluginType() {
	return null;
    }
}
?>
