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

GalleryCoreApi::relativeRequireOnce('modules/core/classes/GalleryPlugin.class');

/**
 * Module meta-info container
 *
 * This is a container for information about a given module.
 *
 * @package GalleryCore
 * @subpackage Classes
 */
class GalleryModule extends GalleryPlugin {

    /**
     * The module group (optional)
     * Used to better organize site admin pages
     *
     * @var array('group'=>group, 'groupLabel'=>localized label) _group
     * @access private
     */
    var $_group;

    /**
     * This module's callbacks.  This is a pipe (|) delimited string containing
     * one or more of the following values:
     *   registerEventListeners
     *   getSiteAdminViews
     *   getItemAdminViews
     *   getUserAdminViews
     *   getSystemLinks
     *   getItemLinks
     *   getItemSummaries
     *
     * eg: getItemAdminViews|getSystemLinks|getItemLinks
     *
     * @var string _callbacks
     * @access private
     */
    var $_callbacks = '';

    /**
     * The version of the GalleryModule API required by this module
     *
     * @var array _requiredModuleApi
     * @access private
     */
    var $_requiredModuleApi;

    /**
     * Return the major and minor version of the GalleryModule API.
     *
     * This follows the same rules as the core API.
     * @see GalleryCoreApi::getApiVersion()
     *
     * @return array major number, minor number
     */
    function getApiVersion() {
	/* TODO: Remove loadItemDetails on next major version bump */
	return array(2, 0);
    }

    /**
     * Register any event listeners that this module requires.  Each module will get a chance to
     * register its event listeners before any events are posted.
     */
    function registerEventListeners() {
    }

    /**
     * Perform the module installation or upgrade, whatever is required.
     *
     * It will do the following:
     * 1. Get the current version of the module (if its already installed)
     * 2. Request that the storage subsystem install this module's database
     *    tables (which will also upgrade any tables that require it)
     * 3. Let the module perform any necessary upgrade tasks.
     * 4. Set the new module version and api requirements into the database
     *
     * Modules should not need to override this method.  Instead they should
     * override the upgrade method and put all their module specific logic
     * there.
     *
     * @return object GalleryStatus a status code
     */
    function installOrUpgrade($bootstrap=false, $statusMonitor=null) {
	global $gallery;

	if ($gallery->getDebug()) {
	    $gallery->debug(sprintf('GalleryModule::installOrUpgrade %s module', $this->getId()));
	}

	if (!GalleryCoreApi::isPluginCompatibleWithApis($this)) {
	    return GalleryStatus::error(ERROR_PLUGIN_VERSION_MISMATCH, __FILE__, __LINE__,
		sprintf('incompatible %s %s', $this->getPluginType(), $this->getId()));
	}

	if ($bootstrap) {
	    $gallery->debug('In bootstrap mode (core module)');
	    /*
	     * If we're in bootstrap mode, then we may not even have a version
	     * table.  If we try to query it, we will cause our current
	     * transaction to abort on some databases.  So, just assume that
	     * there's no installed version.
	     */
	    $installedVersion = null;
	} else {
	    list ($ret, $installedVersion) = $this->getParameter('_version');
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	}

	if ($installedVersion != $this->getVersion()) {
	    if ($gallery->getDebug()) {
		$gallery->debug(sprintf('Configure store for %s module', $this->getId()));
	    }
	    /* The store requires configuration. */
	    $storage =& $gallery->getStorage();
	    $ret = $storage->configureStore($this->getId());
	    if ($ret->isError()) {
		if ($gallery->getDebug()) {
		    $gallery->debug(sprintf('Error: Failed to configure the persistent store, ' .
					    'this is the error stack trace: %s',
					    $ret->getAsText()));
		}
		return $ret->wrap(__FILE__, __LINE__);
	    }

	    if ($gallery->getDebug()) {
		$gallery->debug(sprintf('Upgrade (or install) %s module', $this->getId()));
	    }
	    $ret = $this->upgrade($installedVersion, $statusMonitor);
	    if ($ret->isError()) {
		if ($gallery->getDebug()) {
		    $gallery->debug(sprintf('Error: Failed to upgrade the %s module, this ' .
					    'is the error stack trace: %s', $this->getId(),
					    $ret->getAsText()));
		}
		return $ret->wrap(__FILE__, __LINE__);
	    }

	    if ($gallery->getDebug()) {
		$gallery->debug(sprintf('ConfigureStoreCleanup for %s module', $this->getId()));
	    }
	    $ret = $storage->configureStoreCleanup($this->getId());
	    if ($ret->isError()) {
		if ($gallery->getDebug()) {
		    $gallery->debug(sprintf('Error: Failed to clean up the persistent store, ' .
					   'this is the error stack trace: %s',
					    $ret->getAsText()));
		}
		return $ret->wrap(__FILE__, __LINE__);
	    }

	    /*
	     * Reactivate myself so that any activate based tasks like registering factory
	     * implementations, toolkit operations, etc can happen.  Note that it's possible for
	     * this to successfully deactivate and then fail to activate again.  upgrade() should
	     * get the module read properly but it may fail under some edge cases.
	     */
	    if ($gallery->getDebug()) {
		$gallery->debug(sprintf('Reactivate %s module', $this->getId()));
	    }
	    list ($ret, $redirect) = $this->reactivate();
	    if ($ret->isError()) {
		if ($gallery->getDebug()) {
		    $gallery->debug(sprintf('Error: Failed to reactivate the module, this' .
					    ' is the error stack trace: %s', $ret->getAsText()));
		}
		return $ret->wrap(__FILE__, __LINE__);
	    }

	    if ($gallery->getDebug()) {
		$gallery->debug(sprintf('Update module paramater for the %s module',
					$this->getId()));
	    }
	    $data = array('_version' => $this->getVersion(),
			  '_callbacks' => $this->getCallbacks(),
			  '_requiredCoreApi' => join(',', $this->getRequiredCoreApi()),
			  '_requiredModuleApi' => join(',', $this->getRequiredModuleApi()));
	    foreach ($data as $key => $value) {
		$ret = $this->setParameter($key, $value);
		if ($ret->isError()) {
		    if ($gallery->getDebug()) {
			$gallery->debug(sprintf('Error: Failed to update the module parameter %s' .
						', this is the error stack trace: %s', $key,
						$ret->getAsText()));
		    }
		    return $ret->wrap(__FILE__, __LINE__);
		}
	    }
	}

	if ($gallery->getDebug()) {
	    $gallery->debug(sprintf('Successfully finished installOrUpgrade %s module',
				    $this->getId()));
	}

	return GalleryStatus::success();
    }

    /**
     * Remove this module's plugin parameters and all of its database tables.
     *
     * @return object GalleryStatus a status code
     */
    function uninstall() {
	global $gallery;

	/* Find and remove all module permissions */
	$ret = GalleryCoreApi::unregisterModulePermissions($this->getId());
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	/* Remove onLoadHandlers */
	$ret = GalleryCoreApi::removeOnLoadHandlers($this->getOnLoadHandlerIds());
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

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

	/* Remove all tables */
	$storage =& $gallery->getStorage();
	$ret = $storage->unconfigureStore($this->getId());
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	/* Remove this plugin */
	$ret = parent::uninstall();
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	return GalleryStatus::success();
    }

    /**
     * @see GalleryPlugin::deactivate()
     */
    function deactivate($postDeactivationEvent=true) {
	list ($ret, $redirect) = parent::deactivate($postDeactivationEvent);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	if (!empty($redirect)) {
	    return array(GalleryStatus::success(), $redirect);
	}

	/* Unregister all factory implementations */
	$ret = GalleryCoreApi::unregisterFactoryImplementationsByModuleId($this->getId());
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

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

    /**
     * @see GalleryPlugin::activate()
     *
     * Note: if you add something here, consider adding  the same functionality
     * to installOrUpgrade (see the TODO there).
     */
    function activate($postActivationEvent=true) {
	if (!GalleryCoreApi::isPluginCompatibleWithApis($this)) {
	    return array(GalleryStatus::error(ERROR_PLUGIN_VERSION_MISMATCH, __FILE__, __LINE__,
		sprintf('incompatible %s %s', $this->getPluginType(), $this->getId())), null);
	}

	/* Make sure that we don't need configuration before we allow activation. */
	list ($ret, $needs) = $this->needsConfiguration();
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	if ($needs) {
	    return array(GalleryStatus::error(ERROR_CONFIGURATION_REQUIRED, __FILE__, __LINE__),
			 null);
	}

	list ($ret, $redirect) = parent::activate($postActivationEvent);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	if (!empty($redirect)) {
	    return array(GalleryStatus::success(), $redirect);
	}

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

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

    /**
     * Does this module require configuration before it can be activated?
     *
     * @return array object GalleryStatus a status code
     *               boolean
     */
    function needsConfiguration() {
	return array(GalleryStatus::success(), false);
    }

    /**
     * Is this module recommended to be installed/activated during the installer?
     *
     * Default is false which is a security measure. Only recommend modules
     * that by default do not expose any sensitive data. For example, the
     * debug module shouldn't be recommended because it can show filesystem
     * paths. Also don't recommend installing modules that might change some
     * default gallery behaviour (e.g. the squarethumbs module)
     */
    function isRecommendedDuringInstall() {
	return false;
    }

    /**
     * Allow this module to autoconfigure itself.  It should only autoconfigure if it is
     * marked as needing configuration (see GalleryModule::needsConfiguration) and it should
     * do its best to choose a reasonable configuration.  Return true upon success,
     * even if nothing has to be done to get a successful configuration.  This method
     * should accept all the module's defaults wherever possible.
     *
     * @return array object GalleryStatus a status code
     *               boolean
     */
    function autoConfigure() {
	/* By default we can't auto configure */
	return array(GalleryStatus::success(), false);
    }

    /**
     * Get the name of the GalleryView containing the various site
     * administration views for this module.  Note that the text value is
     * localized since they will be displayed directly to the user.
     * Include group/groupLabel in data to override the values from getGroup().
     *
     * @return array object GalleryStatus a status code
     *               array( array('name' => name, 'view' => view,
     *                 [optional: 'group' => group, 'groupLabel' => label]), ...)
     */
    function getSiteAdminViews() {
	return array(GalleryStatus::success(), array());
    }

    /**
     * Get the list of GalleryViews containing the various item
     * administration views for this module.  The module should check
     * permissions and item type to determine which views are applicable for
     * the authenticated user.  As with getSiteAdminViews(), the view title
     * text must be localized.
     *
     * @param object GalleryItem an item
     * @return array object GalleryStatus a status code
     *               array( array('name' => name, 'view' => view), ...)
     */
    function getItemAdminViews($item) {
	return array(GalleryStatus::success(), array());
    }

    /**
     * Get the list of GalleryViews containing the various user administration
     * views for this module.  The module should check permissions and item
     * type to determine which views are applicable for the authenticated user.
     * As with getSiteAdminViews(), the view title text must be localized.
     *
     * @param object GalleryUser a user
     * @return array object GalleryStatus a status code
     *               array( array('name' => name, 'view' => view), ...)
     */
    function getUserAdminViews($user) {
	return array(GalleryStatus::success(), array());
    }

    /**
     * Get the name of the GalleryView containing the administration view
     * specifically for configuring this module.  It may be one that is also
     * listed in getSiteAdminViews().
     *
     * @return array string view name
     */
    function getConfigurationView() {
	return null;
    }

    /**
     * Return 0 or more system-specific links to an arbitrary module view.  Get
     * the name of the GalleryView containing the administration view
     * specifically for configuring this module.  It may be one that is also
     * listed in getSiteAdminViews().  As with getSiteAdminViews(), the view
     * title text must be localized.
     *
     * @return array object GalleryStatus a status code
     *               array(string linkId => array('text' => 'localized text',
     *                                            'params' => array(key => value, key => value))
     *                     ...)
     */
    function getSystemLinks() {
	return array(GalleryStatus::success(), array());
    }

    /**
     * Return 0 or more item-specific links to an arbitrary module view.
     * These are links to item specific module views.  For example, the
     * comments module uses this to link to the "add comments" view.
     *
     * @param array object GalleryItem
     * @param array (id => 1, id => 1) we want detailed links for these ids
     * @param array id => array(permission => 1, ...) of item permissions
     * @return array object GalleryStatus a status code
     *               array(itemId => array(array('text' => 'localized text',
     *                                           'params' => array(key => value, key => value))
     *                                     ...))
     */
    function getItemLinks($items, $wantsDetailedLinks, $permissions) {
	return array(GalleryStatus::success(), array());
    }

    /**
     * Return module-specific summary content about the item
     *
     * @param array object GalleryItem
     * @param array id => array(permission => 1, ...) of item permissions
     * @return array object GalleryStatus a status code
     *               string HTML content
     */
    function getItemSummaries($items, $permissions) {
	return array(GalleryStatus::success(), array());
    }

    /**
     * TODO: remove this obsolete api.
     * Return module-specific details content about the item. This is localized
     * content specific to a given item.  You can use this to return in-depth
     * item content.  In theory it should be more detailed than the item summaries.
     *
     * @param object GalleryTemplate a template
     * @param object GalleryItem
     * @param array of item permissions
     * @return array object GalleryStatus a status code
     *               string path to template file to include
     */
    function loadItemDetails(&$template, $item, $permissions) {
	return array(GalleryStatus::error(ERROR_UNSUPPORTED_OPERATION, __FILE__, __LINE__), null);
    }

    /**
     * Register any factory implementations.  This is called at module
     * activation time.  All implementations are unregistered when the module is
     * deactivated.
     *
     * @return object GalleryStatus a status code
     */
    function performFactoryRegistrations() {
	return GalleryStatus::success();
    }

    /**
     * Get ids of all onLoadHandlers this module may register.
     * These will be removed from all entities upon module uninstall.
     *
     * @return array of factory impl ids
     */
    function getOnLoadHandlerIds() {
	return array();
    }

    /**
     * Returns a set of short URL rules. Array structure:
     *
     * $rule['match']       An array of view => viewName, subView => subViewName to match
     *                      when generating the short URLs.
     * $rule['pattern']     Proposed short URL style (ie 'v/%path%').
     * $rule['keywords']    Pattern keywords should have the same name as its coresponding
     *                      url parameter key. This is an array of additional keywords
     *                      defined keyword => array(pattern => regex, ignored => 1). If ignored
     *			    is set, the this keyword wont be appended to the queryString.
     * $rule['queryString'] Additional queryString parameters to append.
     * $rule['locked']      If this is set, the user may not change the pattern.
     * $rule['comment']     This should be a localized comment.
     * return array($rule, ...);
     *
     * @return array of short URL rules.
     */
    function getRewriteRules() {
	return array();
    }

    /* Getters and setters below */

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

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

    function getCallbacks() {
	return $this->_callbacks;
    }

    function setCallbacks($callbacks) {
	$this->_callbacks = $callbacks;
    }

    /**
     * @see GalleryPlugin::getPluginType()
     */
    function getPluginType() {
	return 'module';
    }

    function setRequiredModuleApi($requirement) {
	$this->_requiredModuleApi = $requirement;
    }

    function getRequiredModuleApi() {
	return $this->_requiredModuleApi;
    }

    /**
     * Translate string.  Results are cached.
     * @param mixed string or array for translation
     * @param string (optional) attempt to translate post-sprintf string using this value
     *    This allows specific translation of strings like "Edit Album" or "Edit Photo"
     *    with a fallback to translation of "Edit %s" for other %s values.
     *    Currently only input string with single % token is supported.
     * @return translated string
     * @access private
     */
    function _translate($params, $postSprintf=null) {
	if (isset($postSprintf)) {
	    $string = sprintf($params['text'], $postSprintf);
	} else if (is_string($params)) {
	    $string = $params;
	}
	if (isset($string) && GalleryDataCache::containsKey('_translate ' . $string)) {
	    return GalleryDataCache::get('_translate ' . $string);
	}
	if (isset($postSprintf)) {
	    $result = $this->translate($string);
	}
	if (!isset($result) || $result == $string) {
	    $result = $this->translate($params);
	}
	if (isset($string)) {
	    GalleryDataCache::put('_translate ' . $string, $result);
	}
	return $result;
    }
}
?>
