<?php
/*
 * $RCSfile: GalleryTheme.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.62 $ $Date: 2005/09/08 03:33:38 $
 * @package GalleryCore
 * @author Bharat Mediratta <bharat@menalto.com>
 */

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

/**
 * This interface provides functionality to user-interfaces have a
 * customizable theme. A theme should implement this class.
 *
 * @package GalleryCore
 * @subpackage Classes
 */
class GalleryTheme extends GalleryPlugin {

    /**
     * The version of the GalleryTheme API required by this theme
     *
     * @var array _requiredThemeApi
     * @access private
     */
    var $_requiredThemeApi;

    /**
     * Which of the standard theme settings this theme supports
     *
     * @var array _standardSettings
     * @access private
     */
    var $_standardSettings = array();

    /**
     * Constructor
     *
     * Constructor to prevent PHP Notices in upgrader and AdminThemes.inc when
     * old themes with a theme.inc are still in the themes folder
     * The old themes call $this->GalleryTheme() in their constructor.
     *
     * @access public
     */
    function GalleryTheme() {
    }

    /**
     * Return the major and minor version of the GalleryTheme API.
     *
     * This follows the same rules as the core API.
     * @see GalleryCoreApi::getApiVersion()
     *
     * @return array major number, minor number
     */
    function getApiVersion() {
	return array(2, 1);
    }

    /**
     * Return whether the theme uses simple or advanced settings.
     * To support simple settings the theme needs to implement getSettings and validateSettings.
     * To support advanced settings the theme must implement loadSettingsTemplate and
     * handleSettingsRequest.
     *
     * @return boolean true for advanced settings, false for simple
     */
    function isAdvancedSettings() {
	return false;  /* Default to simple */
    }

    /**
     * Return the possible settings that a theme can specify on a global or per
     * item basis.  Used for theme with simple settings ( @see isAdvancedSettings ).
     *
     * Each setting contains:
     *   key:         a unique identifier
     *   name:        a localized, displayable text string
     *   type:        single-select, multiple-select, text-field
     *   choices:     [only valid for single-select, multiple-select type]
     *                array of:
     *                   key:     a unique identifier within this set of choices
     *                   display: a localized displayable text string
     *   value:       the current value for this setting
     *
     * @param int optional itemId
     * @return array object GalleryStatus a status code
     *               settings array
     */
    function getSettings($itemId=null) {
	list ($ret, $params) = $this->fetchParameters($itemId);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	list ($ret, $core) = GalleryCoreApi::loadPlugin('module', 'core');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	/**
	 * For simplicity in upgrading, we don't require themes to delete their old settings.  So
	 * any item may be carting around extra settings that are no longer valid.  We could fix
	 * this in the future, but for now, just leave those out of the settings list.
	 */
	$standard = $this->getStandardSettings();
	$params = array_merge($standard, $params);

	$settings = array();
	if (isset($params['rows']) && isset($standard['rows'])) {
	    $settings[] = array('key' => 'rows',
				'name' => $core->translate('Rows per album page'),
				'type' => 'text-field',
				'typeParams' => array('size' => 2),
				'value' => $params['rows']);
	}

	if (isset($params['columns']) && isset($standard['columns'])) {
	    $settings[] = array('key' => 'columns',
				'name' => $core->translate('Columns per album page'),
				'type' => 'text-field',
				'typeParams' => array('size' => 2),
				'value' => $params['columns']);
	}

	if (isset($params['showImageOwner']) && isset($standard['showImageOwner'])) {
	    $settings[] = array('key' => 'showImageOwner',
				'name' => $core->translate('Show image owners'),
				'type' => 'checkbox',
				'value' => $params['showImageOwner']);
	}

	if (isset($params['showAlbumOwner']) && isset($standard['showAlbumOwner'])) {
	    $settings[] = array('key' => 'showAlbumOwner',
				'name' => $core->translate('Show album owners'),
				'type' => 'checkbox',
				'value' => $params['showAlbumOwner']);
	}

	if (isset($params['showMicroThumbs']) && isset($standard['showMicroThumbs'])) {
	    $settings[] = array('key' => 'showMicroThumbs',
				'name' => $core->translate('Show micro navigation thumbnails'),
				'type' => 'checkbox',
				'value' => $params['showMicroThumbs']);
	}

	if (isset($params['perPage']) && isset($standard['perPage'])) {
	    $settings[] = array('key' => 'perPage',
				'name' => $core->translate('Number of items to show per page'),
				'type' => 'text-field',
				'typeParams' => array('size' => 2),
				'value' => $params['perPage']);
	}

	if (isset($params['sidebarBlocks']) && isset($standard['sidebarBlocks'])) {
	    $settings[] = array('key' => 'sidebarBlocks',
				'name' => $core->translate('Blocks to show in the sidebar'),
				'type' => 'block-list',
				'typeParams' => array('packType' => 'block-list'),
				'value' => $params['sidebarBlocks']);
	}

	if (isset($params['albumBlocks']) && isset($standard['albumBlocks'])) {
	    $settings[] = array('key' => 'albumBlocks',
				'name' => $core->translate('Blocks to show on album pages'),
				'type' => 'block-list',
				'typeParams' => array('packType' => 'block-list'),
				'value' => $params['albumBlocks']);
	}

	if (isset($params['photoBlocks']) && isset($standard['photoBlocks'])) {
	    $settings[] = array('key' => 'photoBlocks',
				'name' => $core->translate('Blocks to show on photo pages'),
				'type' => 'block-list',
				'typeParams' => array('packType' => 'block-list'),
				'value' => $params['photoBlocks']);
	}

	/* ImageFrame settings, if available */
	list ($ret, $imageframe) = GalleryCoreApi::newFactoryInstance('ImageFrameInterface_1_1');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	if (isset($imageframe) &&
	    (isset($params['albumFrame']) ||
	     isset($params['itemFrame']) ||
	     isset($params['photoFrame']))) {
	    list ($ret, $list) = $imageframe->getImageFrameList();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    list ($ret, $sampleUrl) = $imageframe->getSampleUrl($itemId);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    $sample = ' (<a href="' . $sampleUrl . '">'
		    . $core->translate('View Samples') . '</a>)';

	    if (isset($params['albumFrame']) && isset($standard['albumFrame'])) {
		$settings[] = array('key' => 'albumFrame',
				    'name' => $core->translate('Album Frame') . $sample,
				    'type' => 'single-select',
				    'choices' => $list,
				    'value' => $params['albumFrame']);
	    }

	    if (isset($params['itemFrame']) && isset($standard['itemFrame'])) {
		$settings[] = array('key' => 'itemFrame',
				    'name' => $core->translate('Item Frame') . $sample,
				    'type' => 'single-select',
				    'choices' => $list,
				    'value' => $params['itemFrame']);
	    }

	    if (isset($params['photoFrame']) && isset($standard['photoFrame'])) {
		$settings[] = array('key' => 'photoFrame',
				    'name' => $core->translate('Photo Frame') . $sample,
				    'type' => 'single-select',
				    'choices' => $list,
				    'value' => $params['photoFrame']);
	    }
	}

	/* ColorPack setting, if available */
	list ($ret, $colorpack) = GalleryCoreApi::newFactoryInstance('ColorPackInterface_1_0');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	if (isset($colorpack) && isset($params['colorpack'])) {
	    list ($ret, $list) = $colorpack->getColorPacks();
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    $settings[] = array('key' => 'colorpack',
				'name' => $core->translate('Color Pack'),
				'type' => 'single-select',
				'choices' => $list,
				'value' => $params['colorpack']);
	}

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

    /**
     * Return true if all the values are legal, or false if not.  If there are errors,
     * return an array of localized error messages to display for each invalid setting.
     * Used for theme with simple settings ( @see isAdvancedSettings ).
     *
     * @param settings array('key' => array(value, ...), ...)
     * @return array errors in the form of array('key' => 'translated text', ...)
     */
    function validateSettings($settings) {
	$error = array();
	$standard = $this->getStandardSettings();

	if (isset($standard['rows'])) {
	    if (empty($settings['rows']) || !is_numeric($settings['rows'])) {
		$error['rows'] = $this->translate('You must enter a number greater than 0');
	    }
	}

	if (isset($standard['columns'])) {
	    if (empty($settings['columns']) || !is_numeric($settings['columns'])) {
		$error['columns'] = $this->translate('You must enter a number greater than 0');
	    }
	}

	foreach (array('sidebarBlocks', 'albumBlocks', 'photoBlocks') as $blockKey) {
	    if (isset($standard[$blockKey])) {
		if (!empty($settings[$blockKey])) {
		    list ($success, $newValue) =
			$this->packSetting('block-list', $settings[$blockKey]);
		    if (!$success) {
			$error[$blockKey] =
			    $this->translate('Format: [module.BlockName param=value] ...');
		    }
		}
	    }
	}
	return $error;
    }

    /**
     * Convert a setting from a string format into a PHP native format.  The string
     * format is something that could be passed to the browser, like:
     *       [I like [eggs bacon]]
     * The packed format might be:
     *       array("I like", array("eggs", "bacon"))
     *
     * The specific packing depends on the packType variable.
     *
     * @param string the pack type
     * @param string the input value
     * @return mixed the packed form
     */
    function packSetting($packType, $value) {
	$success = true;

	switch ($packType) {
	case 'block-list':
	    /*
	     * Expecting format:
	     * [module.BlockName param1=value1 ...] [module.BlockName param1=value1 ...] ...
	     *
	     * Output is a serialized array of the format:
	     * array(
	     *    array('module.BlockName', array('param1' => 'value1'))
	     *    array('module.BlockName', array('param1' => 'value1')))
	     *
	     * The value can be empty.  There can be any number of blocks.  Blocks can be
	     * repeated.  There can be any number of parameters.
	     */
	    $results = array();
	    if (empty($value)) {
		/* success */
	    } else if (preg_match_all("/\[([\w]+\.[\w]+)\s*(.*?)\]/", $value, $matches)) {
		for ($i = 0; $i < count($matches[0]); $i++) {
		    $record = array($matches[1][$i], array());
		    $matches[2][$i] = trim($matches[2][$i]);
		    if (!empty($matches[2][$i])) {
			$params = preg_split('/\s+/', $matches[2][$i]);
			foreach ($params as $param) {
			    $paramKeyValues = explode('=', $param);
			    if (count($paramKeyValues) == 2) {
				if (!strcasecmp($paramKeyValues[1], "false")) {
				    $paramKeyValues[1] = false;
				} else if (!strcasecmp($paramKeyValues[1], "true")) {
				    $paramKeyValues[1] = true;
				}
				$record[1][$paramKeyValues[0]] = $paramKeyValues[1];
			    } else {
				$success = false;
			    }
			}
		    }
		    $results[] = $record;
		}
	    } else {
		$success = false;
	    }

	    if (!$success) {
		$results = array();
	    }
	    $value = serialize($results);
	}

	return array($success, $value);
    }

    /**
     * Unpack a packed setting (@see GalleryTheme::packSetting)
     *
     * @param string the pack type
     * @param mixed the packed value
     * @return string the unpacked value
     */
    function unpackSetting($packType, $value) {
	$success = true;

	switch ($packType) {
	case 'block-list':
	    /*
	     * Convert to this format:
	     * [module.BlockName param1=value1 ...] [module.BlockName param1=value1 ...] ...
	     *
	     * Input is:
	     * array('module.BlockName', array('param1' => 'value1'))
	     */
	    $result = '';
	    $input = @unserialize($value);
	    if (!is_array($input)) {
		$success = false;
	    } else {
		$i = 0;
		foreach ($input as $record) {
		    if (!is_array($record)) {
			$success = false;
			break;
		    }
		    $result .= ($i ? ' ' : '') . '[' . $record[0];
		    if (!empty($record[1])) {
			foreach ($record[1] as $k => $v) {
			    $v = ($v === false) ? 'false' : ($v === true ? 'true' : $v);
			    $result .= ' ' . $k . '=' . $v;
			}
		    }
		    $result .= ']';
		    $i++;
		}
		if (!$success) {
		    $result = '';
		}
		$value = $result;
	    }
	}
	return array($success, $value);
    }

    /**
     * Load the template with data to define the theme settings.
     * Used for theme with advanced settings ( @see isAdvancedSettings ).
     *
     * @param array object GalleryTemplate the template instance
     * @param array array the form values
     * @param the item id or null for site wide settings
     * @return array object GalleryStatus a status code
     *               string path to the body template
     */
    function loadSettingsTemplate(&$template, &$form, $itemId=null) {
	return array(GalleryStatus::error(ERROR_UNIMPLEMENTED, __FILE__, __LINE__), null);
    }

    /**
     * Let the theme handle the incoming request.
     * Used for theme with advanced settings ( @see isAdvancedSettings ).
     * @see GalleryController::handleRequest
     *
     * @param array the form values
     * @param the item id or null for site wide settings
     * @return array object GalleryStatus a status code
     *               array error messages
     *               string status message (itemId!=null) or status key (itemId==null)
     */
    function handleSettingsRequest($form, $itemId=null) {
	return array(GalleryStatus::error(ERROR_UNIMPLEMENTED, __FILE__, __LINE__), null, null);
    }

    /**
     * @see GalleryPlugin::activate
     */
    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);
	}

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

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

	/* Set the default value for all settings that don't have values */
	list ($ret, $settings) = $this->getSettings();
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	list ($ret, $currentParameters) =
	    GalleryCoreApi::fetchAllPluginParameters('theme', $this->getId());
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	/*
	 * Settings will have the correct value for all parameters, so if it's not in the map
	 * yet we should store it now.
	 */
	foreach ($settings as $setting) {
	    if (!isset($currentParameters[$setting['key']])) {
		$ret = $this->setParameter($setting['key'], $setting['value']);
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}
	    }
	}

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

    /**
     * Perform the module installation or upgrade, whatever is required.
     *
     * It will do the following:
     * 1. Get the current version of the theme (if its already installed)
     * 2. Let the theme perform any necessary upgrade tasks.
     * 3. Set the new theme version and api requirements into the database
     *
     * Themes should not need to override this method.  Instead they should
     * override the upgrade method and put all their theme specific logic
     * there.
     *
     * @return object GalleryStatus a status code
     */
    function installOrUpgrade() {
	global $gallery;

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

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

	if ($gallery->getDebug()) {
	    $gallery->debug('GalleryTheme::installOrUpgrade get the version parameter');
	}

	list ($ret, $installedVersion) = $this->getParameter('_version');
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	if ($gallery->getDebug()) {
	    $gallery->debug('GalleryTheme::installOrUpgrade compare versions');
	}

	if ($installedVersion != $this->getVersion()) {
	    if ($gallery->getDebug()) {
		$gallery->debug(sprintf('GalleryTheme::installOrUpgrade, installed version is %s,' .
					' theme upgrade required', $installedVersion));
	    }

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

	    /* Reactivate myself to perform any activate based tasks like adding new parameters */
	    if ($gallery->getDebug()) {
		$gallery->debug(sprintf('Reactivate %s theme', $this->getId()));
	    }
	    list ($ret, $redirect) = $this->reactivate();
	    if ($ret->isError()) {
		if ($gallery->getDebug()) {
		    $gallery->debug(sprintf('Error: Failed to reactivate the theme, this' .
					    ' is the error stack trace: %s', $ret->getAsText()));
		}
		return $ret->wrap(__FILE__, __LINE__);
	    }

	    if ($gallery->getDebug()) {
		$gallery->debug('GalleryTheme::installOrUpgrade set new theme version etc.');
	    }

	    $data = array('_version' => $this->getVersion(),
			  '_requiredCoreApi' => join(',', $this->getRequiredCoreApi()),
			  '_requiredThemeApi' => join(',', $this->getRequiredThemeApi()));
	    foreach ($data as $key => $value) {
		$ret = $this->setParameter($key, $value);
		if ($ret->isError()) {
		    return $ret->wrap(__FILE__, __LINE__);
		}
	    }
	}

	if ($gallery->getDebug()) {
	    $gallery->debug('GalleryTheme::installOrUpgrade finished successfully');
	}

	return GalleryStatus::success();
    }

    /**
     * Remove this theme from all albums.
     *
     * @return object GalleryStatus a status code
     */
    function uninstall() {
	global $gallery;

	list ($ret, $searchResults) = $gallery->search(
	    'SELECT [GalleryAlbumItem::id] FROM [GalleryAlbumItem] WHERE ' .
	    '[GalleryAlbumItem::theme] = ?', array($this->getId()));
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	for ($ids = array(); $result = $searchResults->nextResult();) {
	    $ids[] = $result[0];
	}
	if (!empty($ids)) {
	    /* Reset albums to default theme */
	    list ($ret, $defaultTheme) =
		GalleryCoreApi::getPluginParameter('module', 'core', 'default.theme');
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	    list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($ids);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	    list ($ret, $albums) = GalleryCoreApi::loadEntitiesById($ids);
	    if ($ret->isError()) {
		GalleryCoreApi::releaseLocks($lockId);
		return $ret->wrap(__FILE__, __LINE__);
	    }
	    foreach ($albums as $album) {
		$album->setTheme($defaultTheme);
		$album->save();
		if ($ret->isError()) {
		    GalleryCoreApi::releaseLocks($lockId);
		    return $ret->wrap(__FILE__, __LINE__);
		}
	    }
	    $ret = GalleryCoreApi::releaseLocks($lockId);
	    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, $defaultTheme) =
	    GalleryCoreApi::getPluginParameter('module', 'core', 'default.theme');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	if ($defaultTheme == $this->getId() && $postDeactivationEvent) {
	    /*
	     * Can't deactivate default theme.  UI doesn't offer this option, so we should
	     * only reach this code if default theme needs to be upgraded.  loadPlugin
	     * will see a redirect url is returned and jump to upgrader (the actual redirect
	     * returned below isn't used).
	     * Allow this deactivate if $postDeactivationEvent is false (during reactivate).
	     */
	    return array(GalleryStatus::success(), array('href' => 'upgrade/'));
	}

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

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

    /**
     * Load the template with data from this view.
     *
     * @param array object GalleryTemplate the template instance
     * @param object GalleryItem the item to display
     * @param array theme parameters
     * @param array results from the view
     * @return array object GalleryStatus a status code
     *               array ('body'/'html' => string template or 'redirect' => array)
     */
    function loadTemplate($view, &$template, $item, $params) {
	global $gallery;

	$theme =& $template->getVariableByReference('theme');
	$theme['useFullScreen'] = false;

	$form =& $template->getVariableByReference('form');
	list ($ret, $viewResults) = $view->loadTemplate($template, $form);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	if (isset($viewResults['redirect'])) {
	    return array(GalleryStatus::success(), $viewResults);
	}

	$urlGenerator =& $gallery->getUrlGenerator();
	$theme['themeUrl'] =
	    $urlGenerator->generateUrl(array('href' => 'themes/' . $this->getId()), false);

	/* By default we're not in preview mode, and are the active user */
	$theme['guestPreviewMode'] = 0;
	$theme['actingUserId'] = $gallery->getActiveUserId();

	list ($ret, $theme['markupType']) =
	    GalleryCoreApi::getPluginParameter('module', 'core', 'misc.markup');
	if ($ret->isError()) {
	    $theme['markupType'] = 'none';
	}

	$theme['params'] = $params;

	/* Unserialize our blocks and preload them if necessary */
	$platform = $gallery->getPlatform();
	$g2Base = dirname(dirname(dirname(dirname(__FILE__))));

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

	foreach (array('sidebarBlocks', 'albumBlocks', 'photoBlocks') as $blockKey) {
	    if (!isset($theme['params'][$blockKey])) {
		continue;
	    }

	    $theme['params'][$blockKey] = unserialize($theme['params'][$blockKey]);

	    $seen = array();
	    foreach ($theme['params'][$blockKey] as $block) {
		$seen[$block[0]][] = $block[1];
	    }

	    foreach ($seen as $key => $blockParams) {
		list ($module, $file) = explode('.', $key);

		if (empty($pluginStatus[$module]['active'])) {
		    continue;
		}

		$path = "modules/$module/Preloads.inc";
		if ($platform->file_exists("$g2Base/$path")) {
		    GalleryCoreApi::relativeRequireOnce($path);
		    $className = "${module}Preloads";
		    $instance = new $className;
		    $ret = $instance->preload($template, $file, $blockParams);
		    if ($ret->isError()) {
			return array($ret->wrap(__FILE__, __LINE__), null);
		    }
		}
	    }
	}

	/* Load color pack if the theme supports them (and they're available) */
	if (!empty($params['colorpack'])) {
	    list ($ret, $colorpack) = GalleryCoreApi::newFactoryInstance('ColorPackInterface_1_0');
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    if (isset($colorpack)) {
		$ret = $colorpack->selectColorPack($template, $params['colorpack']);
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}
	    }
	}

	if ($this->getId() == 'fallbackTheme') {
	    /* If theme failed to load then we're done */
	    $theme['isFallback'] = true;
	    return array(GalleryStatus::success(), $viewResults);
	}

	if (!GalleryCapabilities::can('showSidebarBlocks') && !empty($params['sidebarBlocks'])) {
	    $extractedSidebarBlocks = $theme['params']['sidebarBlocks'];
	    $theme['params']['sidebarBlocks'] = array();
	}

	/*
	 * Figure out what type of view we've got.  This is lame and not very OO.  We should
	 * create a view hierarchy that lets them implement their own showPage() method.
	 */
	switch (strtolower(get_class($view))) {
	case 'useradminview':
	case 'siteadminview':
	case 'itemadminview':
	    $theme['pageType'] = 'admin';
	    $theme['adminTemplate'] = $viewResults['body'];
	    $theme['adminL10Domain'] = $view->getL10Domain();
	    list ($ret, $result) = $this->showAdminPage(
		$template, $item, $params, $viewResults['body']);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    break;

	case 'showitemview':
	    $session =& $gallery->getSession();

	    if ($session->get('theme.guestPreviewMode')) {
		$theme['guestPreviewMode'] = 1;
		list ($ret, $theme['actingUserId']) =
		    GalleryCoreApi::getPluginParameter('module', 'core', 'id.anonymousUser');
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}
	    }

	    /* Albums have their own settings.  Photos use their parent album's settings */
	    if ($item->getCanContainChildren()) {
		$theme['pageType'] = 'album';

		/* If we care about pagination, then figure out the current page and total pages */
		$perPage = $this->getPageSize($params);

		$page = GalleryUtilities::getRequestVariables('page');
		if ($perPage == 0) {
		    $page = 1;
		} else if (empty($page)) {
		    /*
		     * We don't have a page number.  If we have a highlight id, then figure out what
		     * page that id is on and redirect to that page.
		     */
		    $highlightId = GalleryUtilities::getRequestVariables('highlightId');
		    if (!empty($highlightId)) {
			list ($ret, $childIds) = GalleryCoreApi::fetchChildItemIds($item);
			if ($ret->isError()) {
			    return array($ret->wrap(__FILE__, __LINE__), null);
			}

			$page = 1;
			for ($i = 0; $i < sizeof($childIds); $i++) {
			    if ($childIds[$i] == $highlightId) {
				/* Found it */
				$page = ceil(($i+1) / $perPage);
				break;
			    }
			}

			/* Redirect to the new page */
			$redirect = array('view' => 'core.ShowItem', 'itemId' => $item->getId());
			if ($page != 1) {
			    $redirect['page'] = $page;
			}
			return array(GalleryStatus::success(), array('redirect' => $redirect));
		    } else {
			$page = 1;
		    }
		}

		if (!empty($perPage)) {
		    if (empty($page)) {
			$page = 1;
		    }

		    /* Use the pagination to calculate the child item ids to load */
		    $start = $perPage * ($page - 1);
		    list ($ret, $childIds) = GalleryCoreApi::fetchChildItemIds(
			$item, $start, $perPage, $theme['actingUserId']);
		    if ($ret->isError()) {
			return array($ret->wrap(__FILE__, __LINE__), null);
		    }

		    /*
		     * Load up our child count so that we can figure out the max pages.  We do this
		     * after we get the child ids because the 'childCount' common template data also
		     * gets the child counts for the child albums.
		     */
		    $ret = $this->loadCommonTemplateData(
			$template, $item, $params, array('childCount'), $childIds);
		    if ($ret->isError()) {
			return array($ret->wrap(__FILE__, __LINE__), null);
		    }

		    /* Store the total pages in the theme */
		    $theme['totalPages'] = ceil($theme['childCount'] / $perPage);
		    $theme['currentPage'] = $page;

		    /* If our page is over the max, redirect the user to the max page */
		    if ($page > $theme['totalPages'] && $theme['childCount'] > 0) {
			$redirect = array('view' => 'core.ShowItem', 'itemId' => $item->getId());
			if ($theme['totalPages'] != 1) {
			    $redirect['page'] = $theme['totalPages'];
			}
			return array(GalleryStatus::success(), array('redirect' => $redirect));
		    }
		} else {
		    /*
		     * No pagination; load all children (this isn't going to scale, but the theme is
		     * the boss... for now).
		     */
		    list ($ret, $childIds) = GalleryCoreApi::fetchChildItemIds(
			$item, null, null, $theme['actingUserId']);
		    if ($ret->isError()) {
			return array($ret->wrap(__FILE__, __LINE__), null);
		    }
		}

		list ($ret, $result) =
		    $this->showAlbumPage($template, $item, $params, $childIds);
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}
	    } else {
		$theme['pageType'] = 'photo';
		list ($ret, $result) = $this->showPhotoPage($template, $item, $params);
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}
	    }

	    /* Load image frames if the theme supports them (and they're available) */
	    if (!empty($params['albumFrame']) ||
		!empty($params['itemFrame']) ||
		!empty($params['photoFrame'])) {
		list ($ret, $imageframe) =
		    GalleryCoreApi::newFactoryInstance('ImageFrameInterface_1_1');
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}
		if (isset($imageframe)) {
		    $frameIds = array();
		    if ($item->getCanContainChildren()) {
			foreach (array('albumFrame', 'itemFrame') as $key) {
			    if (!empty($params[$key])) {
				$frameIds[] = $theme['params'][$key] = $params[$key];
			    }
			}
		    } else {
			if (!empty($params['photoFrame'])) {
			    $frameIds[] = $theme['photoFrame'] = $params['photoFrame'];
			}
		    }
		    if (!empty($frameIds)) {
			$ret = $imageframe->init($template, $frameIds);
			if ($ret->isError()) {
			    return array($ret->wrap(__FILE__, __LINE__), null);
			}
		    }
		}
	    }
	    break;

	case 'progressbarview':
	    /* We only use this for progressbar views (for now) */
	    $theme['pageType'] = 'progressbar';
	    list ($ret, $result) = $this->showProgressBarPage($template, $item, $params);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    break;

	default: /* module view */
	    $theme['pageType'] = 'module';
	    $theme['moduleTemplate'] = $viewResults['body'];
	    $theme['moduleL10Domain'] = $view->getL10Domain();
	    list ($ret, $result) = $this->showModulePage(
		$template, $item, $params, $viewResults['body']);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    break;
	}

	if (!is_array($result)) {
	    $result = array('body' => sprintf('themes/%s/templates/%s', $this->getId(), $result));
	}

	if (isset($extractedSidebarBlocks)) {
	    $templateAdapter =& $gallery->getTemplateAdapter();

	    /* Render the sidebar blocks and save them */
	    foreach ($extractedSidebarBlocks as $block) {
		$template->setVariable(
		    'SidebarBlock', array('type' => $block[0], 'params' => $block[1]));
		list ($ret, $result['sidebarBlocksHtml'][]) =
		    $template->fetch('gallery:modules/core/templates/SidebarBlock.tpl');
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}
	    }
	}

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

    /**
     * Load commonly used data into theme template
     * Always loaded:
     *   item        item data
     *   children    array of child item data
     * Available keys to include in $dataToLoad parameter:
     *   owner = item owner data
     *      ^if childIds non-empty also fill ownerMap with array of ownerId => owner data
     *   viewCount = number of views for item
     *      ^if childIds non-empty also set viewCount on each child item
     *   childCount = number of children for item
     *      ^if childIds non-empty also set childCount on each child item that canContainChildren
     *   descendentCount = number of descendents for item
     *      ^if childIds non-empty also set descendentCount on each child that canContainChildren
     *   parents = array of ancestor data; also set parent key (direct parent data)
     *   systemLinks = array of array('text'=>.., 'params'=>.., 'moduleId'=>..)
     *   itemLinks = array of id => array('text'=>.., 'params'=>.., 'moduleId'=>..)
     *      ^if childIds non-empty also set itemLinks on each child item
     *   childItemLinksDetailed = boolean.  true if you want detailed item links for children.
     *                            you always get detailed item links for the current item
     *   itemSummaries = set itemSummaries (array of moduleId => html) on each child item
     *   thumbnails = if childIds non-empty then set thumbnail on each child item
     *   pageNavigator = calculate urls for first/back/next/last links for album page navigation
     *   itemNavigator = calculate urls for first/back/next/last links for photo navigation
     *   navThumbnails = load the thumbnails for itemNavigator items
     *   jumpRange = calculate page urls for inter-album navigation (eg, "page: 1, 2 .. 7, 8")
     *       Include 'pageWindowSize'=># in $params to override default of 6
     *   imageViews = loads entity data for resizes and source images, suitable for display
     *                when viewing a single image:
     *                'imageViews' => derivatives,
     *                'sourceImage' => data item,
     *                   ^ contains 'viewInline' boolean, specifying if it can be displayed inline
     *                   ^ contains 'itemTypeName' string, the type of item
     *                   ^ contains 'isSource' member
     *                'imageViewsIndex' => index to the resize currently displayed
     *                'sourceImageViewIndex' => index to the source currently displayed
     *                'fullSizeDimensions' => a string with dimensions of the orig. (eg, "640x480")
     *   permissions array of item permissions, respecting the guest mode flag.
     *               Periods in permissions have been converted to underscores
     *               to make them more Smarty friendly, so if you want to check
     *               a permission in smarty you'd do:
     *                   {if isset($theme.permissions.core_addDataItem)}
     *
     * @param object GalleryItem the item to display
     * @param array theme parameters
     * @param array (string data key, ..) data to load into template
     * @param array (optional) ids of child items to display
     * @return object GalleryStatus a status code
     * @access public
     * @static
     */
    function loadCommonTemplateData(&$template, $item, $params, $dataToLoad,
				    $childIds=array()) {
	global $gallery;

	list ($ret, $core) = GalleryCoreApi::loadPlugin('module', 'core');
	if ($ret->isError()) {
	    return $ret->wrap(__FILE__, __LINE__);
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * Initialize..
	 */
	$theme =& $template->getVariableByReference('theme');
	$load = $childItems = $childData = array();

	foreach ($dataToLoad as $key) {
	    $load[$key] = true;
	}
	if (!empty($childIds)) {
	    list ($ret, $childItems) = GalleryCoreApi::loadEntitiesById($childIds);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	}
	$itemId = $item->getId();
	$allItems = array_merge(array($item), $childItems);
	$allItemIds = array_merge(array($itemId), $childIds);
	$allAlbumIds = array();
	foreach ($allItems as $it) {
	    if ($it->getCanContainChildren()) {
		$allAlbumIds[] = $it->getId();
	    }
	}
	$perPage = $this->getPageSize($params);

	if (!empty($childIds)) {
	    /*
	     * Study all permissions at once so that individual permission checks later
	     * don't lead to multiple database queries.
	     */
	    $ret = GalleryCoreApi::studyPermissions($childIds, $theme['actingUserId']);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * Load additional required entities..
	 */
	$ids = array();
	if (isset($load['owner'])) {
	    $ids[$item->getOwnerId()] = true;
	    $childOwnerIds = array();
	    foreach ($childItems as $child) {
		$ids[$child->getOwnerId()] = true;
		$childOwnerIds[$child->getOwnerId()] = true;
	    }
	    $childOwnerIds = array_keys($childOwnerIds);
	}
	if (isset($load['parents'])) {
	    /*
	     * TODO: Should we have this obey the acting user permission?  It may make navigation
	     * strange for the active user.
	     */
	    list ($ret, $parentSequence) = GalleryCoreApi::fetchParentSequence($itemId);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	    if (!empty($parentSequence)) {
		$ret = GalleryCoreApi::studyPermissions($parentSequence, $theme['actingUserId']);
		if ($ret->isError()) {
		    return $ret->wrap(__FILE__, __LINE__);
		}
	    }
	    foreach ($parentSequence as $id) {
		$ids[$id] = true;
	    }
	}
	if (isset($load['itemNavigator'])) {
	    $navigator = array();
	    $canViewParent = false;
	    if ($item->getParentId() != null) {
		list ($ret, $canViewParent) = GalleryCoreApi::hasItemPermission(
		    $item->getParentId(), 'core.view', $theme['actingUserId']);
		if ($ret->isError()) {
		    return $ret->wrap(__FILE__, __LINE__);
		}
	    }
	    if ($canViewParent) {
		list ($ret, $parent) = GalleryCoreApi::loadEntitiesById($item->getParentId());
		if ($ret->isError()) {
		    return $ret->wrap(__FILE__, __LINE__);
		}
		list ($ret, $peerDataItemIds) = GalleryCoreApi::fetchChildDataItemIds(
						$parent, null, null, $theme['actingUserId']);
		if ($ret->isError()) {
		    return $ret->wrap(__FILE__, __LINE__);
		}
		foreach ($peerDataItemIds as $i => $id) {
		    if ($id == $itemId) {
			$dataItemIndex = $i;
			break;
		    }
		}
		if (isset($dataItemIndex)) {
		    if ($dataItemIndex > 0) {
			$navigator['first'] = $peerDataItemIds[0];
			$navigator['back'] = $peerDataItemIds[$dataItemIndex - 1];
		    }
		    $lastIndex = count($peerDataItemIds) - 1;
		    if ($dataItemIndex < $lastIndex) {
			$navigator['next'] = $peerDataItemIds[$dataItemIndex + 1];
			$navigator['last'] = $peerDataItemIds[$lastIndex];
		    }
		    foreach ($navigator as $id) {
			$ids[$id] = true;
		    }
		}
	    }
	    if (empty($navigator)) {
		unset($load['itemNavigator']);
	    }
	}
	if (!empty($ids)) {
	    list ($ret, $list) = GalleryCoreApi::loadEntitiesById(array_keys($ids));
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	    foreach ($list as $it) {
		$entities[$it->getId()] = $it;
	    }
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * Always load 'item' and 'children'
	 */
	$theme['item'] = $item->getMemberData();
	if (!isset($theme['children'])) {
	    $theme['children'] = array();
	    foreach ($childItems as $child) {
		$tmp = $child->getMemberData();
		$theme['children'][] = $tmp;
	    }
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'owner'
	 */
	if (isset($load['owner'])) {
	    $theme['item']['owner'] = $entities[$item->getOwnerId()]->getMemberData();
	    for ($i = 0; $i < count($theme['children']); $i++) {
		$theme['children'][$i]['owner'] =
		    $entities[$theme['children'][$i]['ownerId']]->getMemberData();
	    }
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'viewCount'
	 */
	if (isset($load['viewCount'])) {
	    list ($ret, $viewCount) = GalleryCoreApi::fetchItemViewCounts($allItemIds);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	    $theme['item']['viewCount'] = $viewCount[$itemId];
	    $childData[] = 'viewCount';
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'childCount'
	 */
	if (isset($load['childCount']) && !isset($theme['childCount'])) {
	    list ($ret, $childCount) =
		GalleryCoreApi::fetchChildCounts($allAlbumIds, $theme['actingUserId']);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	    foreach ($allAlbumIds as $id) {
		if (!isset($childCount[$id])) {
		    $childCount[$id] = 0;
		}
	    }
	    $theme['childCount'] = isset($childCount[$itemId]) ? $childCount[$itemId] : 0;
	    $childData[] = 'childCount';
	}
	if (isset($theme['childCount'])) {
	    $theme['item']['childCount'] = $theme['childCount'];
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'descendentCount'
	 */
	if (isset($load['descendentCount'])) {
	    list ($ret, $descendentCount) =
		GalleryCoreApi::fetchDescendentCounts($allAlbumIds, $theme['actingUserId']);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	    foreach ($allAlbumIds as $id) {
		if (!isset($descendentCount[$id])) {
		    $descendentCount[$id] = 0;
		}
	    }
	    $theme['descendentCount'] =
		isset($descendentCount[$itemId]) ? $descendentCount[$itemId] : 0;
	    $theme['item']['descendentCount'] = $theme['descendentCount'];
	    $childData[] = 'descendentCount';
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'parents'
	 */
	if (isset($load['parents'])) {
	    $theme['parents'] = array();
	    foreach ($parentSequence as $id) {
		list ($ret, $canSee) =
		    GalleryCoreApi::hasItemPermission($id, 'core.view', $theme['actingUserId']);
		if ($ret->isError()) {
		    return $ret->wrap(__FILE__, __LINE__);
		}
		if ($canSee) {
		    $theme['parents'][] = $entities[$id]->getMemberData();
		}
	    }
	    $theme['parent'] = empty($theme['parents']) ? null :
		$theme['parents'][count($theme['parents']) - 1];
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'systemLinks', 'itemLinks', 'itemSummaries'
	 * Load links and content from all active modules
	 */
	if (isset($load['systemLinks'])
		|| isset($load['itemLinks']) || isset($load['itemSummaries'])) {
	    $urlGenerator =& $gallery->getUrlGenerator();

	    list ($ret, $permissions) =
		GalleryCoreApi::fetchPermissionsForItems($allItemIds, $theme['actingUserId']);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }

	    if (isset($load['systemLinks'])) {
		$theme['systemLinks'] = array();
	    }
	    if (isset($load['itemLinks'])) {
		$itemLinks = array($itemId => array());
		foreach ($childIds as $id) {
		    $itemLinks[$id] = array();
		}
	    }
	    if (isset($load['itemSummaries']) && !empty($childIds)) {
		foreach ($childIds as $id) {
		    $itemSummaries[$id] = array();
		}
		$childData[] = 'itemSummaries';
	    }

	    list ($ret, $moduleStatus) = GalleryCoreApi::fetchPluginStatus('module');
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }

	    if (empty($load['childItemLinksDetailed'])) {
		/* We always want detailed links for the current item */
		$wantsDetailedLinks = array($itemId => 1);
	    } else {
		/* We want detailed links for everybody */
		$wantsDetailedLinks = array_flip($allItemIds);
	    }

	    foreach ($moduleStatus as $moduleId => $status) {
		if (empty($status['active'])) {
		    continue;
		}
		$callbacks = array_flip(explode('|', $status['callbacks']));

		$required = false;
		foreach (array('systemLinks' => 'getSystemLinks',
			       'itemLinks' => 'getItemLinks',
			       'itemSummaries' => 'getItemSummaries') as $key => $callbackKey) {
		    if (isset($load[$key]) && isset($callbacks[$callbackKey])) {
			$required = true;
			break;
		    }
		}

		if (!$required) {
		    /* This module doesn't have anything we need.  Don't bother with it */
		    continue;
		}

		list ($ret, $module) = GalleryCoreApi::loadPlugin('module', $moduleId);
		if ($ret->isError()) {
		    if ($ret->getErrorCode() & ERROR_PLUGIN_VERSION_MISMATCH) {
			continue;
		    }
		    return $ret->wrap(__FILE__, __LINE__);
		}

		if (isset($load['systemLinks']) && isset($callbacks['getSystemLinks'])) {
		    /* We don't use the acting user for system links -- it's too confusing */
		    list ($ret, $links) = $module->getSystemLinks();
		    if ($ret->isError()) {
			return $ret->wrap(__FILE__, __LINE__);
		    }
		    /* Add url/moduleId keys.. */
		    foreach ($links as $key => $value) {
			$theme['systemLinks'][$moduleId . '.' . $key] = $value;
		    }
		}

		if (isset($itemLinks) && isset($callbacks['getItemLinks'])) {
		    list ($ret, $links) = $module->getItemLinks(
			$allItems, $wantsDetailedLinks, $permissions);
		    if ($ret->isError()) {
			return $ret->wrap(__FILE__, __LINE__);
		    }

		    /* Add url/moduleId keys.. */
		    foreach ($links as $id => $list) {
			foreach ($list as $link) {
			    $link['moduleId'] = $moduleId;
			    $itemLinks[$id][] = $link;
			}
		    }
		}

		if (isset($itemSummaries) && isset($callbacks['getItemSummaries'])) {
		    list ($ret, $content) = $module->getItemSummaries($childItems, $permissions);
		    if ($ret->isError()) {
			return $ret->wrap(__FILE__, __LINE__);
		    }
		    foreach ($content as $id => $html) {
			if (!empty($html)) {
			    $itemSummaries[$id][$moduleId] = $html;
			}
		    }
		}
	    }

	    if (isset($itemLinks)) {
		foreach (array_keys($itemLinks) as $id) {
		    usort($itemLinks[$id], array('GalleryTheme', '_sortItemLinks'));
		}
		$theme['itemLinks'] = $itemLinks[$itemId];
		$childData[] = 'itemLinks';
	    }
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'thumbnails'
	 */
	if (isset($load['thumbnails']) && !empty($childIds)) {
	    list ($ret, $thumbnail) = GalleryCoreApi::fetchThumbnailsByItemIds($childIds);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }
	    foreach (array_keys($thumbnail) as $id) {
		if (!($thumbnail[$id]->getWidth() && $thumbnail[$id]->getHeight())) {
		    list ($ret, $thumbnail[$id]) =
			GalleryCoreApi::rebuildDerivativeCache($thumbnail[$id]->getId());
		    if ($ret->isError()) {
			return $ret->wrap(__FILE__, __LINE__);
		    }
		}
		$thumbnail[$id] = $thumbnail[$id]->getMemberData();
	    }
	    $childData[] = 'thumbnail';
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * Populate data in children list..
	 */
	if (!empty($childData)) {
	    foreach (array_keys($theme['children']) as $i) {
		$id = $theme['children'][$i]['id'];
		foreach ($childData as $key) {
		    if (isset(${$key}[$id])) {
			$theme['children'][$i][$key] = ${$key}[$id];
		    }
		}
	    }
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'pageNavigator'
	 */
	if (isset($load['pageNavigator']) && isset($theme['totalPages'])) {
	    $page = GalleryUtilities::getRequestVariables('page');
	    if (empty($page)) {
		$page = 1;
	    }

	    /* Set up the navigator */
	    $navigator = array();
	    if ($page > 1) {
		$navigator['first']['urlParams'] = $navigator['back']['urlParams'] =
		    array('view' => 'core.ShowItem', 'itemId' => $itemId);
		if ($page - 1 != 1) {
		    $navigator['back']['urlParams']['page'] = $page - 1;
		}
	    }
	    if ($page < $theme['totalPages']) {
		$navigator['next']['urlParams'] = array(
		    'view' => 'core.ShowItem', 'itemId' => $itemId, 'page' => $page + 1);
		$navigator['last']['urlParams'] = array(
		    'view' => 'core.ShowItem', 'itemId' => $itemId, 'page' => $theme['totalPages']);
	    }
	    $theme['navigator'] = $navigator;
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'itemNavigator', 'navThumbnails'
	 */
	if (isset($load['itemNavigator'])) {
	    $thumbTable = array();
	    if (isset($load['navThumbnails'])) {
		list ($ret, $thumbTable) = GalleryCoreApi::fetchThumbnailsByItemIds($navigator);
		if ($ret->isError()) {
		    return $ret->wrap(__FILE__, __LINE__);
		}
	    }
	    foreach ($navigator as $key => $id) {
		$navigator[$key] = array('urlParams' => array('view' => 'core.ShowItem',
							      'itemId' => $id),
					 'item' => $entities[$id]->getMemberData());
		if (isset($thumbTable[$id])) {
		    $navigator[$key]['thumbnail'] = $thumbTable[$id]->getMemberData();
		}
	    }
	    $theme['navigator'] = $navigator;
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'jumpRange'
	 */
	if (isset($load['jumpRange']) && isset($theme['totalPages'])) {
	    $page = GalleryUtilities::getRequestVariables('page');
	    if (empty($page)) {
		$page = 1;
	    }
	    $windowSize = isset($params['pageWindowSize']) ? $params['pageWindowSize'] : 6;
	    $jumpRange = array();
	    $lowerPage = max($page - (int)($windowSize / 2), 1);
	    $upperPage = min($page + (int)($windowSize / 2), $theme['totalPages']);
	    if ($upperPage == 0) {
		$upperPage = 1;
	    }
	    if ($upperPage == $theme['totalPages']) {
		$lowerPage = max($upperPage - $windowSize, 1);
	    } else if ($lowerPage == 1) {
		$upperPage = min($lowerPage + ($windowSize-1), $theme['totalPages']);
	    }
	    for ($i = $lowerPage; $i <= $upperPage; $i++) {
		$jumpRange[] = $i;
	    }
	    if ($lowerPage > 1) {
		array_unshift($jumpRange, 1);
	    }
	    if ($upperPage < $theme['totalPages']) {
		$jumpRange[] = $theme['totalPages'];
	    }
	    $theme['jumpRange'] = $jumpRange;
	}

	/*
	 * --------------------------------------------------------------------------------------
	 * 'imageViews'
	 */
	if (isset($load['imageViews'])) {
	    /*
	     * Figure out all possible views of this item that the user can see and
	     * get them into an acceptable format for the template engine.
	     */
	    $imageViews = array();
	    $can = array();
	    list ($ret, $permissions) =
		GalleryCoreApi::getPermissions($itemId, $theme['actingUserId']);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }

	    /* If the user can see resized versions, add those to the list */
	    if (isset($permissions['core.viewResizes'])) {
		/* Load the resizes */
		list ($ret, $resizes) = GalleryCoreApi::fetchResizesByItemIds(array($itemId));
		if ($ret->isError()) {
		    return $ret->wrap(__FILE__, __LINE__);
		}

		if (!empty($resizes)) {
		    foreach ($resizes[$itemId] as $resize) {
			/* Rebuild the derivative if we don't have its dimensions */
			if ($resize->getWidth() == 0 || $resize->getHeight() == 0) {
			    list ($ret, $resize) =
				GalleryCoreApi::rebuildDerivativeCacheIfNotCurrent(
				    $resize->getId());
			    if ($ret->isError()) {
				return $ret->wrap(__FILE__, __LINE__);
			    }
			}

			$tmp = $resize->getMemberData();
			$tmp['viewInline'] = 1;
			$imageViews[] = $tmp;
		    }
		}
	    }

	    /* If the user can see the full version, add it to the list */
	    $sourceImage = null;
	    if (isset($permissions['core.viewSource'])) {
		/* Add the full version */
		list ($ret, $preferred) = GalleryCoreApi::fetchPreferredsByItemIds(array($itemId));
		if ($ret->isError()) {
		    return $ret->wrap(__FILE__, __LINE__);
		}

		/* Show the preferred item, if it's there */
		if (empty($preferred)) {
		    $sourceImage = $item->getMemberData();
		    $sourceImage['viewInline'] = $item->canBeViewedInline();
		} else {
		    $sourceImage = $preferred[$itemId]->getMemberData();
		    $sourceImage['viewInline'] = true;
		}
		$sourceImage['itemTypeName'] = $item->itemTypeName();
		$sourceImage['isSource'] = true;
		$imageViews[] = $sourceImage;
		$sourceImageViewIndex = sizeof($imageViews)-1;
	    }

	    /* If all else fails, just show the thumbnail. */
	    if (empty($imageViews)) {
		/* Load the thumbnail */
		list ($ret, $thumbnails) = GalleryCoreApi::fetchThumbnailsByItemIds(array($itemId));
		if ($ret->isError()) {
		    return $ret->wrap(__FILE__, __LINE__);
		}

		if (!empty($thumbnails)) {
		    $tmp = $thumbnails[$itemId]->getMemberData();
		    $tmp['viewInline'] = true;
		    $imageViews[] = $tmp;
		}
	    }

	    $imageViewsIndex = GalleryUtilities::getRequestVariables('imageViewsIndex');
	    if (empty($imageViewsIndex)) {
		$imageViewsIndex = 0;
	    }

	    if (empty($sourceImage['width'])) {
		$fullSizeDimensions = $sourceImage['itemTypeName'][0];
	    } else {
		$fullSizeDimensions = $core->translate(
		    array('text' => '%dx%d',
			  'arg1' => $sourceImage['width'],
			  'arg2' => $sourceImage['height']));
	    }

	    /* Don't let the index overflow the images array */
	    $imageViewsIndex = min($imageViewsIndex, count($imageViews) - 1);
	    if (isset($sourceImageViewIndex)) {
		$theme['sourceImageViewIndex'] = $sourceImageViewIndex;
	    }
	    $theme['imageViews'] = $imageViews;
	    $theme['sourceImage'] = $sourceImage;
	    $theme['imageViewsIndex'] = $imageViewsIndex;
	    $theme['fullSizeDimensions'] = $fullSizeDimensions;
	}

	/* -------------------------------------------------------------------------------------- */
	if (isset($load['permissions']) && !isset($theme['permissions'])) {
	    list ($ret, $permissions) =
		GalleryCoreApi::getPermissions($itemId, $theme['actingUserId']);
	    if ($ret->isError()) {
		return $ret->wrap(__FILE__, __LINE__);
	    }

	    foreach (array_keys($permissions) as $perm) {
		$theme['permissions'][str_replace('.', '_', $perm)] = 1;
	    }
	}

	$template->setVariable('theme', $theme);

	return GalleryStatus::success();
    }

    /**
     * Sort an array of associative arrays on the 'text' key
     * @access private
     */
    function _sortItemLinks($a, $b) {
	return strcmp($a['text'], $b['text']);
    }

    /**
     * Return the number of items per page, or 0 if there is no pagination in this theme.
     *
     * @param array the theme parameters
     * @return int
     */
    function getPageSize($params) {
	if (!empty($params['rows']) && !empty($params['columns'])) {
	    return $params['rows'] * $params['columns'];
	}

	if (!empty($params['perPage'])) {
	    return $params['perPage'];
	}

	return 0;
    }

    /**
     * Load all the necessary template data to render a page for an album
     *
     * @param array object GalleryTemplate the template instance
     * @param object GalleryAlbumItem the album item to render.
     * @param array the theme parameters
     * @param int the child item ids
     * @return array object GalleryStatus a status code
     *         string path to a template file or array(html/redirect)
     * @access private
     */
    function showAlbumPage(&$template, $item, $params, $childIds) {
	return array(GalleryStatus::success(), null);
    }

    /**
     * Load all the necessary template data to render a page for a single item
     *
     * @param array object GalleryTemplate the template instance
     * @param object GalleryItem the item to render.  Can be any subclass of GalleryItem
     * @param array the theme parameters
     * @return array object GalleryStatus a status code
     *         string path to a template file or array(html/redirect)
     * @access private
     */
    function showPhotoPage(&$template, $item, $params) {
	return array(GalleryStatus::success(), null);
    }

    /**
     * Load all the necessary template data to render a page for an
     * administrative (or other) view.
     *
     * @param array object GalleryTemplate the template instance
     * @param object GalleryItem the item to render.
     * @param array the theme parameters
     * @param string the body template file from the view
     * @return array object GalleryStatus a status code
     *         string path to a template file or array(html/redirect)
     * @access private
     */
    function showAdminPage(&$template, $item, $params, $templateFile) {
	return array(GalleryStatus::success(), null);
    }

    /**
     * Load all the necessary template data to render a page for a
     * module view (any views that aren't user, site, or item admin
     * eg slideshow or members list)
     *
     * @param array object GalleryTemplate the template instance
     * @param object GalleryItem the item to render
     * @param array the theme parameters
     * @param string the body template file from the view
     * @return array object GalleryStatus a status code
     *         string path to a template file or array(html/redirect)
     * @access private
     */
    function showModulePage(&$template, $item, $params, $templateFile) {
	return array(GalleryStatus::success(), null);
    }

    /**
     * Load all the necessary template data to render an error page
     *
     * @param array object GalleryTemplate the template instance
     * @return array object GalleryStatus a status code
     *         string path to a template file or array(html/redirect)
     * @access private
     */
    function showErrorPage(&$template) {
	return array(GalleryStatus::success(), null);
    }

    /**
     * Load all the necessary template data to render a progress bar page
     *
     * @param array object GalleryTemplate the template instance
     * @param object GalleryItem the item to render
     * @param array the theme parameters
     * @return array object GalleryStatus a status code
     *         string path to a template file or array(html/redirect)
     * @access private
     */
    function showProgressBarPage(&$template, $item, $params) {
	return array(GalleryStatus::success(), null);
    }

    /**
     * Split the HTML content into its various component pieces.
     *
     * @param string the main html (<head>, <body>, etc)
     * @param array any extra html that we've generated, like sidebar HTML
     * @return array('headHtml' => ..., 'bodyHtml' => ...)
     */
    function splitHtml($mainHtml, $extraHtml) {
	if (preg_match('|<head>(.*)</head>.*<body.*?>(.*)</body>|s', $mainHtml, $matches) == 1) {
	    $results = array('headHtml' => $matches[1], 'bodyHtml' => $matches[2]);
	} else {
	    $results = array('bodyHtml' => $mainHtml);
	}

	/* If we extracted the sidebar, it'll be in our extra html, so move that over */
	if (isset($extraHtml['sidebarBlocksHtml'])) {
	    $results['sidebarBlocksHtml'] = $extraHtml['sidebarBlocksHtml'];
	}

	return $results;
    }

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

    function setRequiredThemeApi($requirement) {
	$this->_requiredThemeApi = $requirement;
    }

    function getRequiredThemeApi() {
	return $this->_requiredThemeApi;
    }

    function setStandardSettings($standardSettings) {
	$this->_standardSettings = $standardSettings;
    }

    function getStandardSettings() {
	return $this->_standardSettings;
    }
}
?>
