<?php
/*
 * $RCSfile: GalleryPluginHelper_simple.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.32 $ $Date: 2005/09/04 20:11:07 $
 * @package GalleryCore
 * @author Bharat Mediratta <bharat@menalto.com>
 */

/**
 * Track all plugins and their state
 *
 * @package GalleryCore
 * @subpackage Helpers
 */
class GalleryPluginHelper_simple {

    /**
     * Load and initialize the given plugin
     *
     * @param string the plugin type (eg, module, theme, etc)
     * @param string the plugin id
     * @param bool should we ignore version mismatches (default: no)
     * @param int the depth of recursion (don't set this- it's used internally)
     * @return array object GalleryStatus a status code
     *               object the plugin
     * @static
     */
    function loadPlugin($pluginType, $pluginId, $ignoreVersionMismatch=false, $depth=0) {
	global $gallery;

	if ($gallery->getDebug()) {
	    $gallery->debug(sprintf('Loading plugin %s', $pluginId));
	}

	$cacheKey = "GalleryPluginHelper::loadPlugin($pluginType, $pluginId)";
	if (!GalleryDataCache::containsKey($cacheKey)) {

	    switch($pluginType) {
	    case 'module':
		$pluginSuperClass = 'GalleryModule';
		$pluginClass = $pluginId . 'Module';
		break;

	    case 'theme':
		$pluginSuperClass = 'GalleryTheme';
		$pluginClass = $pluginId . 'Theme';
		break;

	    default:
		return array(GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
						  "pluginType = $pluginType"), null);
	    }

	    if (!class_exists($pluginClass)) {
		if ($gallery->getDebug()) {
		    $gallery->debug('Class not defined, trying to include it.');
		}

		$pluginBaseDir = dirname(__FILE__) . '/../../../../';
		$pluginFile = sprintf('%ss/%s/%s.inc', $pluginType, $pluginId, $pluginType);

		GalleryCoreApi::relativeRequireOnce(
		    sprintf('modules/core/classes/%s.class', $pluginSuperClass));

		$platform = $gallery->getPlatform();
		if (!$platform->file_exists($pluginBaseDir . $pluginFile)) {
		    /*
		     * If we have a bad path, it may be because our cached plugin list is out of
		     * date -- in which case we should flush the cache and try again.
		     */
		    if ($depth == 0) {
			GalleryDataCache::remove(
			    "GalleryPluginHelper::fetchPluginStatus($pluginType)");
			GalleryDataCache::removeFromDisk(
			    array('type' => $pluginType,
				  'itemId' => 'GalleryPluginHelper_fetchPluginStatus',
				  'id' => '_all'));
			list ($ret, $plugin) =
			    GalleryPluginHelper_simple::loadPlugin($pluginType, $pluginId,
								   $ignoreVersionMismatch, $depth + 1);
			if ($ret->isError()) {
			    return array($ret->wrap(__FILE__, __LINE__), null);
			}
		    } else {
			return array(GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
						      $pluginBaseDir . $pluginFile), null);
		    }
		}

		GalleryCoreApi::relativeRequireOnce($pluginFile);

		if (!class_exists($pluginClass)) {
		    return array(GalleryStatus::error(ERROR_BAD_PLUGIN, __FILE__, __LINE__,
						      "Class $pluginClass does not exist"), null);
		}
	    }

	    $plugin = new $pluginClass();
	    /* Store it in our table */
	    GalleryDataCache::put($cacheKey, $plugin, true);
	} else {
	    $plugin = GalleryDataCache::get($cacheKey);
	}

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

	if (!$ignoreVersionMismatch) {
	    if ($gallery->getDebug()) {
		$gallery->debug(sprintf('Check the version of the %s plugin', $pluginId));
	    }

	    /* Verify that the versions match */
	    list ($ret, $status) = GalleryPluginHelper_simple::fetchPluginStatus($pluginType);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

	    if (isset($status[$pluginId]) &&
		($pluginId != 'core' || $pluginType != 'module')) {
		$s = $status[$pluginId];

		if (isset($s['active']) &&
		    isset($s['version']) &&
		    ($s['version'] != $plugin->getVersion() ||
		     !GalleryPluginHelper_simple::isPluginCompatibleWithApis($plugin))) {

		    /* Try to deactivate the plugin */
		    list ($ret, $redirectInfo) = $plugin->deactivate();
		    if ($ret->isError()) {
			return array($ret->wrap(__FILE__, __LINE__), null);
		    }

		    if (!empty($redirectInfo)) {
			/*
			 * We were unable to automatically deactivate the plugin!
			 * Forcibly abort this request and jump to the upgrader so this
			 * module can be upgraded.
			 */
			$gallery->debug("Unable to deactivate $pluginId, jumping to upgrader.");
			$gallery->debug_r($redirectInfo);
			$urlGenerator =& $gallery->getUrlGenerator();
			header('Location: ' .
			       $urlGenerator->getCurrentUrlDir(true) . 'upgrade/index.php');
			exit;
		    }

		    /*  Return a handleable error */
		    return array(GalleryStatus::error(ERROR_PLUGIN_VERSION_MISMATCH,
						      __FILE__, __LINE__,
				     sprintf('[%s] db: (v: %s core api: %s, %s api: %s) '.
					     'code: (v: %s core api: %s, %s api: %s) ',
					     $pluginId,
					     $s['version'],
					     join('/', $s['requiredCoreApi']),
					     $pluginType,
					     join('/', $s[$pluginType == 'module' ?
							  'requiredModuleApi' :
							  'requiredThemeApi']),
					     $plugin->getVersion(),
					     join('/', GalleryCoreApi::getApiVersion()),
					     $pluginType,
					     join('/',
						  $pluginType == 'module' ?
						  GalleryModule::getApiVersion() :
						  GalleryTheme::getApiVersion()))),
				 $plugin);
		}
	    }
	    if ($gallery->getDebug()) {
		$gallery->debug(sprintf('The version of the %s plugin is ok', $pluginId));
	    }
	}

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

    /**
     * Return true if the plugin is compatible with the current API versions.  False otherwise.
     *
     * @param object GalleryPlugin instance
     * @return bool true if the plugin is compatible
     * @static
     */
    function isPluginCompatibleWithApis($plugin) {
	$pluginType = $plugin->getPluginType();
	return GalleryUtilities::isCompatibleWithApi(
	    $plugin->getRequiredCoreApi(), GalleryCoreApi::getApiVersion()) &&
	    (($pluginType == 'module' &&
	      GalleryUtilities::isCompatibleWithApi(
		  $plugin->getRequiredModuleApi(), GalleryModule::getApiVersion())) ||
	     ($pluginType == 'theme' &&
	      GalleryUtilities::isCompatibleWithApi(
		  $plugin->getRequiredThemeApi(), GalleryTheme::getApiVersion())));
    }

    /**
     * Convenience method to retrieve a plugin parameter
     *
     * @param string the plugin type
     * @param string the plugin id
     * @param string the parameter name
     * @param string optional item id
     * @return array object GalleryStatus a status code
     *               string a value
     * @static
     */
    function getParameter($pluginType, $pluginId, $parameterName, $itemId=0) {
	global $gallery;

	if ($gallery->getDebug()) {
	    $gallery->debug(sprintf('getParameter %s for %s plugin', $parameterName, $pluginId));
	}

	/* Convert null to 0, just in case */
	if ($itemId == null) {
	    $itemId = 0;
	}

	list ($ret, $params) =
	    GalleryPluginHelper_simple::fetchAllParameters($pluginType, $pluginId, $itemId);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	/* Return the value, or null if the param doesn't exist */
	if (!isset($params[$parameterName])) {
	    return array(GalleryStatus::success(), null);
	} else {
	    return array(GalleryStatus::success(), $params[$parameterName]);
	}
    }

    /**
     * Get all the parameters for this plugin
     *
     * @param string the type of the plugin
     * @param string the id of the plugin
     * @param integer the id of item (or null for a global setting)
     * @return array object GalleryStatus a status code
     *               array (parameterName => parameterValue)
     * @static
     */
    function fetchAllParameters($pluginType, $pluginId, $itemId=0) {
	global $gallery;
	if (empty($pluginType) || empty($pluginId)) {
	    return array(GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
					      sprintf("Missing pluginType [%s] or pluginId [%s]",
						      $pluginType,
						      $pluginId)),
			 null);
	}

	/* Convert null to 0, just in case */
	if ($itemId == null) {
	    $itemId = 0;
	}

	$cacheKey = "GalleryPluginHelper::fetchAllParameters($pluginType, $pluginId, $itemId)";
	if (!GalleryDataCache::containsKey($cacheKey)) {
	    $data =& GalleryDataCache::getFromDisk(array('type' => $pluginType,
							 'itemId' => $itemId,
							 'id' => $pluginId));
	    if (!isset($data)) {
		$query = '
		  SELECT
		    [GalleryPluginParameterMap::parameterName],
		    [GalleryPluginParameterMap::parameterValue]
		  FROM
		    [GalleryPluginParameterMap]
		  WHERE
		    [GalleryPluginParameterMap::pluginType] = ?
		    AND
		    [GalleryPluginParameterMap::pluginId] = ?
		    AND
		    [GalleryPluginParameterMap::itemId] = ?
		  ';

		list ($ret, $searchResults) =
		    $gallery->search($query, array($pluginType, $pluginId, $itemId));
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}

		$data = array();
		while ($result = $searchResults->nextResult()) {
		    $data[$result[0]] = $result[1];
		}

		GalleryDataCache::putToDisk(array('type' => $pluginType,
						  'itemId' => $itemId,
						  'id' => $pluginId),
					    $data);
	    }
	    GalleryDataCache::put($cacheKey, $data);
	} else {
	    $data = GalleryDataCache::get($cacheKey);
	}

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

    /**
     * Get the status of all plugins of a given type
     *
     * @param string the plugin type (eg 'module', 'theme')
     * @param bool true if we want to ignore the cache?
     * @return array object GalleryStatus as status code
     *                      array (moduleId => array('active' => true/false,
     *                                               'available' => true/false)
     * @static
     */
    function fetchPluginStatus($pluginType, $ignoreCache=false) {
	global $gallery;

	$cacheKey = "GalleryPluginHelper::fetchPluginStatus($pluginType)";
	if ($ignoreCache || !GalleryDataCache::containsKey($cacheKey)) {
	    if (!$ignoreCache) {
		$plugins =& GalleryDataCache::getFromDisk(
		    array('type' => $pluginType,
			  'itemId' => 'GalleryPluginHelper_fetchPluginStatus',
			  'id' => '_all'));
	    }
	    if (!isset($plugins)) {
		list ($ret, $plugins) = GalleryPluginHelper_simple::fetchPluginList($pluginType);
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}

		$platform = $gallery->getPlatform();

		/* Scan modules directory for installed modules */
		switch ($pluginType) {
		case 'module':
		    $pluginsDir = dirname(__FILE__) . '/../../../../modules/';
		    $pluginFile = 'module.inc';
		    break;

		case 'theme':
		    $pluginsDir = dirname(__FILE__) . '/../../../../themes/';
		    $pluginFile = 'theme.inc';
		    break;

		default:
		    return array(GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
						      sprintf("Wrong pluginType [%s]",
							      $pluginType)),
				 null);
		}

		if ($dir = $platform->opendir($pluginsDir)) {
		    while ($pluginId = $platform->readdir($dir)) {
			if ($pluginId{0} == '.') {
			    continue;
			}
			if (!$platform->is_dir($pluginsDir . $pluginId)) {
			    continue;
			}

			$path = $pluginsDir . $pluginId . '/' . $pluginFile;
			if ($platform->file_exists($path)) {
			    $plugins[$pluginId]['available'] = 1;

			    $frameworkParams = array('version', 'callbacks', 'requiredCoreApi');
			    if ($pluginType == 'module') {
				$frameworkParams[] = 'requiredModuleApi';
			    } else {
				$frameworkParams[] = 'requiredThemeApi';
			    }

			    foreach ($frameworkParams as $paramName) {
				list ($ret, $plugins[$pluginId][$paramName]) =
				    GalleryPluginHelper_simple::getParameter(
					$pluginType, $pluginId, '_' . $paramName);
				if ($ret->isError()) {
				    return array($ret->wrap(__FILE__, __LINE__), null);
				}

				/* Separate out the major/minor version. */
				if (!strncmp($paramName, 'required', 8)) {
				    $tmp = split(',', $plugins[$pluginId][$paramName]);
				    // DEBUG: remove these lines below when we
				    // commit; make sure that we bump all module
				    // versions instead so that the module
				    // version reqs aren't empty
				    if (empty($tmp) || count($tmp) < 2) {
					$tmp = array(-1, -1);
				    }
				    $plugins[$pluginId][$paramName] =
					array((int)$tmp[0], (int)$tmp[1]);
				}
			    }
			}
		    }
		    $platform->closedir($dir);
		}

		/* Find and remove plugins that are active, but not installed */
		foreach ($plugins as $pluginId => $pluginStatus) {
		    if (!isset($pluginStatus['available'])) {
			$gallery->debug("Plugin $pluginId no longer installed");
			unset($plugins[$pluginId]);
		    }
		}

		if ($pluginType == 'module') {
		    /* Force the core module's status */
		    $plugins['core']['active'] = 1;
		    $plugins['core']['available'] = 1;
		}

		GalleryDataCache::putToDisk(array('type' => $pluginType,
						  'itemId' => 'GalleryPluginHelper_fetchPluginStatus',
						  'id' => '_all'),
					    $plugins);
	    }
	    GalleryDataCache::put($cacheKey, $plugins);
	} else {
	    $plugins = GalleryDataCache::get($cacheKey);
	}

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

    /**
     * Return a plugin list by plugin type
     *
     * @param string the plugin type
     * @return array GalleryStatus a status code
     *               array of (pluginId => ('active' => boolean))
     * @static
     */
    function fetchPluginList($pluginType) {
	global $gallery;

	if (empty($pluginType)) {
	    return array(GalleryStatus::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
					      "Missing plugin type"),
			 null);
	}

	$cacheKey = "GalleryPluginHelper::fetchPluginList($pluginType)";
	if (!GalleryDataCache::containsKey($cacheKey)) {
	    $query = '
	    SELECT
	      [GalleryPluginMap::pluginId],
	      [GalleryPluginMap::active]
	    FROM
	      [GalleryPluginMap]
	    WHERE
	      [GalleryPluginMap::pluginType] = ?
	    ';

	    list ($ret, $searchResults) = $gallery->search($query, array($pluginType));
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

	    $data = array();
	    while ($result = $searchResults->nextResult()) {
		$data[$result[0]] = array('active' => $result[1]);
	    }
	    GalleryDataCache::put($cacheKey, $data);
	} else {
	    $data = GalleryDataCache::get($cacheKey);
	}

	return array(GalleryStatus::success(), $data);
    }
}
?>
