<?php
/*
 * Gallery - a web based photo album viewer and editor
 * Copyright (C) 2000-2008 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.
 */

GalleryCoreApi::requireOnce('modules/core/classes/GalleryRepository.class');
GalleryCoreApi::requireOnce('modules/core/classes/GalleryRepositoryUtilities.class');

/**
 * Common base class for Downloaded Plugins and Language Manager controllers.
 * @package GalleryCore
 * @subpackage UserInterface
 * @author Tim Almdal <tnalmdal@shaw.ca
 * @version $Revision: 17582 $
 */
class AdminRepositoryDownloadAndInstallController extends GalleryController {

    /**
     * Download specified packages to the local repository cache and perform installation.
     *
     * @todo: Show a summary page (or at least a link to it) which contains details about
     * the exact tasks that were performed and any errors that were encountered.
     *
     * @param array $installPackages packages to install
     * @param array an array of GalleryRepository
     * @return GalleryStatus a status code
     */
    function performDownloadAndInstallation($installPackages, $repositories) {
	global $gallery;
	$session =& $gallery->getSession();
	$platform =& $gallery->getPlatform();
	$phpVm = $gallery->getPhpVm();
	$templateAdapter =& $gallery->getTemplateAdapter();

	/*
	 * Get the original plugin status from before we start making changes so that we can
	 * try to counteract any activate/deactivate ripple effects.
	 */
	foreach (array('module', 'theme') as $pluginType) {
	    list ($ret, $pluginStatus[$pluginType]) = $this->_fetchPluginStatus($pluginType, false);
	    if ($ret) {
		return $ret;
	    }
	}

	list ($ret, $module) = GalleryCoreApi::loadPlugin('module', 'core');
	if ($ret) {
	    return $ret;
	}
	$titleText = $module->translate('Updating Packages');
	$templateAdapter->updateProgressBar($titleText, '', 0);

	/* Create download file list. */
	list ($ret, $sourcedFiles) = 
	    $this->_createDownloadFileList($installPackages, $repositories);

	$totalActions = $this->_countInstallActions($sourcedFiles);

	$status = array('error' => array());

	/* Download files. */
	$currentAction = 0;

	$callback = array('method' => array($templateAdapter, 'updateProgressBar'),
		  'title' => $titleText, 'current' => 0, 'total' => $totalActions);

	foreach ($sourcedFiles as $source => $files) {
	    $repository = $repositories[$source];

	    foreach ($files as $pluginType => $plugins) {
		foreach ($plugins as $pluginId => $pluginDownloadData) {
		    $packageUrls = $pluginDownloadData['files'];
		    $pluginName = $pluginDownloadData['name'];

		    $downloadStatus = array();
		    list($ret, $reactivatePlugin, $downloadStatus) = 
			$repository->downloadPackagesForPlugin($pluginType, $pluginId, 
			    $pluginDownloadData, $callback);
		    if ($ret) {
			$gallery->debug('downloadSourceFiles, failed: ' . $ret->getAsText());
			return $ret;
		    }
		    if (!empty($downloadStatus)) {
			$status['error'] = array_merge($status['error'], $downloadStatus);
		    }

		    /*
		     * Our plugin status caches are no longer valid because we've just installed
		     * some new code.  Load the status and ignore the cache which forces a cache
		     * update.  @todo: add a real cache flushing method in the api instead
		     */
		    list ($ret, $ignored) = GalleryCoreApi::fetchPluginStatus($pluginType, true);
		    if ($ret) {
			return $ret;
		    }

		    /*********************************************************
		     * This next block is duplicated in PluginCallback.
		     * @todo: Refactor this code duplication away
		     */

		    $templateAdapter->updateProgressBar($titleText,
			sprintf($module->translate('Cleaning up obsolete files %s'), $pluginName),
			$currentAction++ / $totalActions);

		    $repository->removeObsoleteFiles($pluginType, $pluginId);

		    $templateAdapter->updateProgressBar(
			$titleText, sprintf($module->translate('Activating %s'), $pluginName),
			$currentAction / $totalActions);

		    if ($reactivatePlugin) {
			/**
			 * Some plugins may already be loaded (e.g. the default theme, or
			 * authentication modules) so PHP will not let us evaluate the newly
			 * downloaded code.  This means that we can't upgrade those modules
			 * via DP; the site admin must do it by hand on the AdminPlugins page.
			 * For now, just notify the user.
			 *
			 * @todo convert this to a two-phase approach so that we can reactivate
			 *       plugins that are already loaded at this point.
			 */
			if ($phpVm->class_exists($pluginId . $pluginType)) {
			    $status['error']['cantUpgradeInUse'][] = $pluginName;
			} else {
			    list ($ret, $plugin) =
				GalleryCoreApi::loadPlugin($pluginType, $pluginId, true);
			    if ($ret) {
				return $ret;
			    }

			    $ret = $plugin->installOrUpgrade();
			    if ($ret) {
				return $ret;
			    }

			    if ($pluginType == 'module') {
				list ($ret, $autoConfigured) = $plugin->autoConfigure();
				if ($ret) {
				    return $ret;
				}
			    } else {
				/* Themes don't need this step */
				$autoConfigured = true;
			    }

			    $isActive = !empty($pluginStatus[$pluginType][$pluginId]['active']);
			    $notInstalled = empty($pluginStatus[$pluginType][$pluginId]['version']);

			    if ($autoConfigured && ($isActive || $notInstalled)) {
				list ($ret, $redirect) = $plugin->activate();
				if ($ret) {
				    return $ret;
				}
				/* Ignore the redirect */
			    }

			    $status['updated'][] = $pluginName;
			}
		    }

		    /*********************************************************/
		}
	    }
	}

	$ret = $this->_synchronizeLanguages($sourcedFiles, $callback);
	if ($ret) {
	    return $ret;
	}

	/* Update progress bar. */
	if (!empty($status['error'])) {
	    $message = $module->translate('Update completed with errors.');
	} else {
	    $message = $module->translate('Update complete.');
	}
	$templateAdapter->updateProgressBar($titleText, $message, 1);

	/* Show link to return to the previously selected tab. */
	$redirect['view'] = 'core.SiteAdmin';
	$redirect['subView'] = 'core.AdminRepository';

	$session->putStatus($status);
	$urlGenerator =& $gallery->getUrlGenerator();
	$templateAdapter->completeProgressBar($urlGenerator->generateUrl($redirect), true);
	return null;
    }

    /**
     * Create a list of all the files that need to be downloaded
     * @param array $installPackages packages to install
     * @param array $repositories an array of GalleryRepository
     * @return array GalleryStatus a status code
     * 		     array list of corresponding files for the specified packages
     */
    function _createDownloadFileList($installPackages, $repositories) {
	/* Create download file list. */
	$sourcedFiles = array();
	foreach ($installPackages as $source => $packages) {
	    if (!isset($repositories[$source])) {
		continue;
	    }

	    list ($ret, $tmp) = $repositories[$source]->getDownloadFileList($packages);
	    if ($ret) {
		return array($ret, null);
	    }
	    $sourcedFiles[$source] = empty($sourcedFiles[$source])
		? $tmp
		: array_merge($sourcedFiles[$source], $tmp);
	}
	return array(null, $sourcedFiles);
    }

    /**
     * Calculate the total number of actions for the progress notifications
     * @param array $sourcedFiles list of files that were downloaded
     * @return int number of actions
     */
    function _countInstallActions($sourcedFiles) {
	$totalActions = 0;
	foreach ($sourcedFiles as $source => $files) {
	    foreach ($files as $pluginType => $plugins) {
		foreach ($plugins as $pluginId => $pluginDownloadData) {
		    /*
		     * 2 actions (preverify and download) for all files except
		     * the descriptor which we just download.
		     */
		    $totalActions += 2 * count($pluginDownloadData['files']) - 1;

		    /* 2 extra action for each plugin (language sync, cleanup) */
		    $totalActions += 2;
		}
	    }
	}
	return $totalActions;
    }

    /**
     * Synchronize the active language packages with the newly downloaded packages
     * @param array $sourcedFiles list of files that were downloaded
     * @param array $callback progress notification callback
     * @return GalleryStatus a status code
     */
    function _synchronizeLanguages($sourcedFiles, &$callback) {
	list ($ret, $module) = GalleryCoreApi::loadPlugin('module', 'core');
	if ($ret) {
	    return $ret;
	}

	$synchronizeText = $module->translate('Synchronizing Language Packs');

	foreach ($sourcedFiles as $source => $files) {
	    foreach ($files as $pluginType => $plugins) {
		foreach ($plugins as $pluginId => $ignored) {
		    $percentage = ++$callback['current'] / $callback['total'];
		    call_user_func($callback['method'], $callback['title'], $synchronizeText, 
			$percentage);

		    $ret = GalleryCoreApi::removeTranslationsForPlugin($pluginType, $pluginId);
		    if ($ret) {
			return $ret;
		    }

		    $ret = GalleryCoreApi::installTranslationsForPlugin($pluginType, $pluginId);
		    if ($ret) {
			return $ret;
		    }
		}
	    }
	}
	return null;
    }

    /**
     * Passthrough to GalleryCoreApi::fetchPluginStatus, used by test code to
     * allow us to inject mock plugins.
     * @see GalleryCoreApi::fetchPluginStatus
     * @access protected
     */
    function _fetchPluginStatus($pluginType, $ignoreCache) {
	return GalleryCoreApi::fetchPluginStatus($pluginType, $ignoreCache);
    }

    /**
     * Update the repository index, and scan all installed plugins and update
     * GalleryPluginPackageMap with a complete listing of everything that's installed.
     *
     * @todo Show a summary page (or at least a link to it) which contains details about
     * the exact tasks that were performed and any errors that were encountered.
     *
     * @param array $pluginStatus status of all available installed plugins
     * @param array $repositories the repositories available for new plugins
     * @param array $redirect generateUrl params defining the view to return to
     * @return GalleryStatus a status code
     */
    function updatePluginStatus($pluginStatus, $repositories, $redirect) {
	global $gallery;
	$templateAdapter =& $gallery->getTemplateAdapter();
	$platform =& $gallery->getPlatform();

	$status = array();
	list ($ret, $module) = GalleryCoreApi::loadPlugin('module', 'core');
	if ($ret) {
	    return $ret;
	}
	$heading = $module->translate('Updating Repository Index');
	$templateAdapter->updateProgressBar($heading, '', 0);

	/* Erase all plugin data, so that a full scan drops old entries */
	$ret = GalleryCoreApi::removeAllMapEntries('GalleryPluginPackageMap');
	if ($ret) {
	    return $ret;
	}

	/* Update the index */
	$currentProgress = 0;
	$indexWeight = 5;
	$increment = 1.0 / ($indexWeight * count($repositories) + count($pluginStatus['module']) +
			    count($pluginStatus['theme']));

	foreach ($repositories as $source => $repository) {
	    list ($ret, $message) = GalleryRepository::translateRepositoryName($source);
	    if ($ret) {
		return $ret;
	    }

	    $templateAdapter->updateProgressBar($heading, $message, $currentProgress);
	    $ret = $repository->downloadIndex();
	    if ($ret) {
		/** @todo: Internationalize this error message */
		$status['error']['download'][] = $ret->getErrorMessage();
	    }
	    $currentProgress += $indexWeight * $increment;
	}

	$heading = $module->translate('Scanning plugins');
	$sSlash = 's' . $platform->getDirectorySeparator();

	if (!isset($repository)) {
	    /*
	     * The loop below needs a working repository, but we don't have one because all of our
	     * existing indexes are missing or corrupted.  So create a new bogus one expressly for
	     * the purpose of calling GalleryRepository::scanPlugin().
	     */
	    $repository = new GalleryRepository();
	    $repository->init('bogus');
	}

	foreach (array('module', 'theme') as $pluginType) {
	    foreach (array_keys($pluginStatus[$pluginType]) as $pluginId) {
		$gallery->guaranteeTimeLimit(30);

		$templateAdapter->updateProgressBar(
			$heading, $pluginType . $sSlash . $pluginId, $currentProgress);
		$ret = $repository->scanPlugin($pluginType, $pluginId);
		if ($ret && !($ret->getErrorCode() & ERROR_STORAGE_FAILURE)) {
		    /*
		     * Something is wrong with this plugin. Maybe it's a 3rd party plugin w/o
		     * MANIFEST file, maybe it has no revisions in the po files, maybe the
		     * module.inc is foobar. Just log and ignore it.
		     */
		    if ($gallery->getDebug()) {
			$gallery->debug_r($ret);
		    }
		    $status['error']['scanPlugin'][] = $pluginId;
		} else if ($ret) {
		    return $ret;
		}
		$currentProgress += $increment;
	    }
	}

	$templateAdapter->updateProgressBar(
	    $module->translate('Update Plugin List'), $module->translate('Done.'), 1);

	$session =& $gallery->getSession();
	$session->putStatus($status);

	$urlGenerator =& $gallery->getUrlGenerator();
	$templateAdapter->completeProgressBar($urlGenerator->generateUrl($redirect), true);

	return null;
    }
}
?>