<?php
/*
 * $RCSfile: RewriteHelper.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.
 */
/**
 * @package Rewrite
 * @version $Revision: 1.31 $ $Date: 2005/08/30 08:09:03 $
 * @author Douglas Cau <douglas@cau.se>
 */

/**
 * Required class
 */
GalleryCoreApi::relativeRequireOnce('modules/core/classes/GalleryTemplate.class');

/**
 * Status codes
 */
define('REWRITE_STATUS_OK', 0);
define('REWRITE_STATUS_APACHE_NO_MOD_REWRITE', 1);
define('REWRITE_STATUS_APACHE_UNABLE_TO_TEST', 2);
define('REWRITE_STATUS_HTACCESS_MISSING', 3);
define('REWRITE_STATUS_HTACCESS_CANT_READ', 4);
define('REWRITE_STATUS_HTACCESS_CANT_WRITE', 5);
define('REWRITE_STATUS_HTACCESS_READY', 6);
define('REWRITE_STATUS_BAD_KEYWORD', 7);
define('REWRITE_STATUS_HTACCESS_MISMATCH', 8);
define('REWRITE_STATUS_MULTISITE', 9);

/**
 * A helper class for the RewriteUrlGenerator class
 *
 * @package Rewrite
 * @subpackage Classes
 */
class RewriteHelper {

    /**
     * Return valid Gallery2 .htaccess content
     *
     * @return array object GalleryStatus a status code
     *               string valid Gallery2 .htaccess
     */
    function getTemplate($rewriteRules, $embedded=false) {
	global $gallery;
	$urlGenerator =& $gallery->getUrlGenerator();

	$Htaccess = array();
	$Htaccess['g2Prefix'] = GALLERY_FORM_VARIABLE_PREFIX;
	$Htaccess['mainPhp'] = GALLERY_MAIN_PHP;
	$Htaccess['rules'] = $rewriteRules;

	list ($ret, $Htaccess['galleryDirectory']) =
		GalleryCoreApi::getPluginParameter('module', 'rewrite', 'galleryLocation');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	list ($ret, $status) = GalleryCoreApi::getPluginParameter('module', 'rewrite', 'status');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	$status = unserialize($status);
	$Htaccess['needOptions'] = $status['needOptions'];

	$Htaccess['directory'] = $Htaccess['galleryDirectory'];
	$Htaccess['rewriteBase'] = $Htaccess['galleryDirectory'];
	$Htaccess['baseFile'] = GALLERY_MAIN_PHP;
	if ($embedded) {
	    $Htaccess['baseFile'] = $urlGenerator->_baseFile;
	    list ($ret, $Htaccess['directory']) =
		    GalleryCoreApi::getPluginParameter('module', 'rewrite', 'embeddedLocation');
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

	    $Htaccess['rewriteBase'] = $Htaccess['directory'];
	    $components = parse_url($Htaccess['baseFile']);
	    if (isset($components['host'])) {
		$Htaccess['directory'] = '';
	    }
	}
	$Htaccess['matchBaseFile'] = preg_quote($Htaccess['baseFile']);
	$Htaccess['baseFile'] .= (strpos($Htaccess['baseFile'], '?') === false) ? '?' : '&';

	$template = new GalleryTemplate(dirname(__FILE__) . '/../templates');
	$template->setVariable('Htaccess', $Htaccess);
	list ($ret, $content) = $template->fetch('Htaccess.tpl', 'modules_rewrite');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

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

    /**
     * Return .htaccess absolute file name
     *
     * @return array object GalleryStatus a status code
     *               string .htaccess file name
     */
    function getHtaccessPath($embedded=false) {
	if ($embedded) {
	    list ($ret, $path) =
		    GalleryCoreApi::getPluginParameter('module', 'rewrite', 'embeddedHtaccess');
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

	    return array(GalleryStatus::success(), $path . '/.htaccess');
	}

	return array(GalleryStatus::success(), GALLERY_CONFIG_DIR . '/.htaccess');
    }

    /**
     * Checks if the .htaccess file is valid with Gallery2 data.
     *
     * @return array object GalleryStatus a status code
     *               int rewrite status code
     */
    function checkFile($embedded=false) {
	global $gallery;
	$platform = $gallery->getPlatform();

	list ($ret, $file) = RewriteHelper::getHtaccessPath($embedded);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	if ($platform->file_exists($file)) {
	    /* Can't read the .htaccess file */
	    if (!$platform->is_readable($file)) {
		return array(GalleryStatus::success(), REWRITE_STATUS_HTACCESS_CANT_READ);
	    }

	    /* Can't write the .htaccess file */
	    if (!$platform->is_writeable($file)) {
		return array(GalleryStatus::success(), REWRITE_STATUS_HTACCESS_CANT_WRITE);
	    }
	} else {
	    /* If we have write access to the directory we can create the file */
	    if (!$platform->is_writeable(dirname($file))) {
		return array(GalleryStatus::success(), REWRITE_STATUS_HTACCESS_MISSING);
	    }
	}

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

    /**
     * Checks if the embedded htaccess file is up to date. Out of synced
     * files may occure if one install/uninstall modules in standalone mode and has Gallery
     * setup with an embedded entry point.
     *
     * @return array object GalleryStatus a status code
     *               int rewrite status code
     */
    function checkSync() {
	GalleryCoreApi::relativeRequireOnce('modules/rewrite/classes/RewriteMap.class');
	global $gallery;
	$platform = $gallery->getPlatform();

	list ($ret, $activeRules) = RewriteMap::getActiveRules();
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	list($ret, $code, $rewriteRules) = RewriteHelper::parseActiveRules($activeRules);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	if ($code != REWRITE_STATUS_OK) {
	    return array(GalleryStatus::error(ERROR_UNKNOWN, __FILE__, __LINE__), null);
	}
	list ($ret, $galleryHtaccess) = RewriteHelper::getTemplate($rewriteRules, true);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	list ($ret, $embeddedPath) = RewriteHelper::getHtaccessPath(true);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	if (!$platform->is_readable($embeddedPath)) {
	    return array(GalleryStatus::success(), REWRITE_STATUS_HTACCESS_CANT_READ);
	}

	$embeddedHtaccess = implode('', $platform->file($embeddedPath));
	$embeddedHtaccess = str_replace("\r\n", "\n", $embeddedHtaccess);

	if (!preg_match(
		'/\# BEGIN Url Rewrite section(.+)\# END Url Rewrite section/s',
		$embeddedHtaccess, $embeddedSection)) {
	    return array(GalleryStatus::success(), REWRITE_STATUS_HTACCESS_MISMATCH);
	}

	if (!preg_match(
		'/\# BEGIN Url Rewrite section(.+)\# END Url Rewrite section/s',
		$galleryHtaccess, $gallerySection)) {
	    return array(GalleryStatus::success(), REWRITE_STATUS_HTACCESS_MISMATCH);
	}

	if ($embeddedSection[1] != $gallerySection[1]) {
	    return array(GalleryStatus::success(), REWRITE_STATUS_HTACCESS_MISMATCH);
	}

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

    /**
     * Writes Gallery2 data to the .htaccess file.
     *
     * @return array object GalleryStatus a status code
     *               int rewrite status code
     */
    function writeFile($rewriteRules, $embedded=false) {
	global $gallery;
	$platform = $gallery->getPlatform();

	list ($ret, $file) = RewriteHelper::getHtaccessPath($embedded);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	$htaccess = '';
	if (is_array($rewriteRules)) {
	    list ($ret, $htaccess) = RewriteHelper::getTemplate($rewriteRules, $embedded);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	}

	if ($platform->file_exists($file)) {
	    if (!$platform->is_readable($file)) {
		return array(GalleryStatus::success(), REWRITE_STATUS_HTACCESS_CANT_READ);
	    }

	    $oldHtaccess = implode('', $platform->file($file));
	    $newHtaccess = preg_replace(
		'/\# BEGIN Url Rewrite section(.+)\# END Url Rewrite section(\n\r|\n|\r|\x85)/s',
		'{gallerySection}', $oldHtaccess);

	    if (strpos($newHtaccess, '{gallerySection}') !== false) {
		$newHtaccess = str_replace('{gallerySection}', $htaccess, $newHtaccess);
	    } else {
		$newHtaccess .= "\n" . $htaccess;
	    }
	} else {
	    $newHtaccess = $htaccess;
	}

	/* Write the new file */
	if ($fd = @$platform->fopen($file, 'w')) {
	    $platform->fwrite($fd, $newHtaccess);
	    $platform->fclose($fd);
	} else {
	    return array(GalleryStatus::success(), REWRITE_STATUS_HTACCESS_CANT_WRITE);
	}

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

    /**
     * Returns one of the following codes:
     *   REWRITE_STATUS_OK                              everything is fine
     *   REWRITE_STATUS_APACHE_NO_MOD_REWRITE           no mod rewrite support
     *   REWRITE_STATUS_APACHE_UNABLE_TO_TEST           unable to properly test mod_rewrite
     *   REWRITE_STATUS_MULTISITE       		can't test mod_rewrite in multisite
     *
     * @return array object GalleryStatus a status code
     *               int rewrite status code.
     */
    function checkModRewrite() {
	global $gallery;
	$urlGenerator =& $gallery->getUrlGenerator();

	list ($ret, $status) = GalleryCoreApi::getPluginParameter('module', 'rewrite', 'status');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	/* autoConfigure may be called from upgrader before 'status' is set: */
	$status = !empty($status) ? unserialize($status) : array();
	if (isset($status['forced'])) {
	    return array(GalleryStatus::success(), REWRITE_STATUS_OK);
	}

	if ($gallery->getConfig('galleryBaseUrl')) {
	    return array(GalleryStatus::success(), REWRITE_STATUS_MULTISITE);
	}
	$baseUrl = $gallery->getConfig('galleryBaseUrl');
	if (empty($baseUrl)) {
	    $baseUrl = preg_replace('{(install|upgrade)/index\.php.*}', '',
				    $urlGenerator->getCurrentUrlDir(true));
	}
	$baseUrl .= 'modules/rewrite/data/';
	$components = parse_url($baseUrl);
	$path = $components['path'];

	/*
	 * Testing mod_rewrite functionality.  In order for mod_rewrite to work
	 * properly, it needs a .htaccess file containing a RewriteBase
	 * directive that matches the URL of its containing directory.
	 */
	if (!strncmp($path, '/gallery2/', 10)) {
	    $target = 'gallery2';
	} else if (!strncmp($path, '/gallery/', 9)) {
	    $target = 'gallery';
	} else {
	    $target = 'custom';
	}

	$fetch = $baseUrl . "mod_rewrite_no_options/$target/Rewrite.txt";
	list ($body, $headers, $url) = GalleryCoreAPI::fetchWebPage($fetch);

	if ($headers == 'HTTP/1.1 200 OK' && $body == "PASS_REWRITE\n") {
	    $ret = GalleryCoreApi::setPluginParameter('module', 'rewrite', 'status',
		serialize(array('needOptions' => false)));
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

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

	/*
	 * Apache mod_rewrite needs Options +FollowSymlinks, and we might need to explicit
	 * require it in the .htaccess file.
	 */
	$fetch = $baseUrl . "mod_rewrite/$target/Rewrite.txt";
	list ($body, $headers, $url) = GalleryCoreAPI::fetchWebPage($fetch);

	if ($headers == 'HTTP/1.1 200 OK' && $body == "PASS_REWRITE\n") {
	    $ret = GalleryCoreApi::setPluginParameter('module', 'rewrite', 'status',
		serialize(array('needOptions' => true)));
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }

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

	/*
	 * If we fail trying to test with the custom setup dont whine too much. Instead give
	 * the user directions to edit the custom .htaccess file and then test again.
	 */
	if ($target == 'custom') {
	    return array(GalleryStatus::success(), REWRITE_STATUS_APACHE_UNABLE_TO_TEST);
	}

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

    /**
     * Parses active rules and returns rewrite rules.
     *
     * @param array active rules
     * @param object RewriteModule (optional) passed in during activate/upgrade
     * @return array rewrite rules
     */
    function parseActiveRules($activeRules, $rewriteModule=null) {
	list ($ret, $accessList) = RewriteHelper::getAccessList();
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null, null);
	}
	$rewriteRules = array();
	foreach (array_keys($activeRules) as $moduleId) {
	    if ($moduleId == 'rewrite' && isset($rewriteModule)) {
		/* Avoid PLUGIN_VERSION_MISMATCH during upgrade by passing in module */
		$module = $rewriteModule;
	    } else {
		list($ret, $module) = GalleryCoreApi::loadPlugin('module', $moduleId);
		if ($ret->isError()) {
		    if ($ret->getErrorCode() & ERROR_PLUGIN_VERSION_MISMATCH) {
			/*
			 * Add CONFIGURATION_REQUIRED code to more gracefully abort upgrade
			 * if a dependent module for one of our rules also needs upgrading.
			 */
			$ret->addErrorCode(ERROR_CONFIGURATION_REQUIRED);
		    }
		    return array($ret->wrap(__FILE__, __LINE__), null, null);
		}
	    }

	    $rules = $module->getRewriteRules();
	    foreach (array_keys($activeRules[$moduleId]) as $ruleId) {
		$settings = array();
		$settings['condition'] = array();
		$keywords = array(
		    '_path_' => array('ignore' => 1),
		    'itemId' => array('pattern' => '([0-9]+)'));
		if (isset($rules[$ruleId]['keywords'])) {
		    $keywords = array_merge($keywords, $rules[$ruleId]['keywords']);
		}

		if (!empty($rules[$ruleId]['restrict'])) {
		    foreach ($rules[$ruleId]['restrict'] as $key => $value) {
			$settings['condition'][] = '%{QUERY_STRING} ' . $key . '=' . $value;
		    }
		}

		$queryString = array();
		if (isset($rules[$ruleId]['match'])) {
		    $queryString = $rules[$ruleId]['match'];
		}

		if (!empty($rules[$ruleId]['queryString'])) {
		    $queryString = array_merge($queryString, $rules[$ruleId]['queryString']);
		}

		if (isset($rules[$ruleId]['forbidden'])) {
		    $settings['forbidden'] = 1;
		}

		if (isset($rules[$ruleId]['restrict'])) {
		    $settings['condition'] = array_merge($settings['condition'], $accessList);
		}

		if (empty($settings['condition'])) {
		    unset($settings['condition']);
		}

		$settings['flags'] = array('QSA', 'L');
		if (isset($rules[$ruleId]['flags'])) {
		    $settings['flags'] = array_merge($rules[$ruleId]['flags'], array('L'));
		}
		$settings['flags'] = join(',', $settings['flags']);

		$urlPattern = str_replace('%path%', '%_path_%',
			$activeRules[$moduleId][$ruleId]['pattern']);
		list ($ret, $code) = RewriteHelper::_parseRule($urlPattern, $keywords,
			$queryString, $rewriteRules, $settings);
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null, null);
		}
		if ($code != REWRITE_STATUS_OK) {
		    return array(GalleryStatus::success(), $code, null);
		}
	    }
	}

	usort($rewriteRules, array('RewriteHelper', '_sortRules'));
	return array(GalleryStatus::success(), REWRITE_STATUS_OK, $rewriteRules);
    }

    /**
     * Returns an array of rewrite condition based access list.
     *
     * @return array object GalleryStatus a status code
     *               array of Rewrite conditions
     */
    function getAccessList() {
	list ($ret, $allow) = GalleryCoreApi::getPluginParameter('module', 'rewrite', 'accessList');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	$allow = array_merge(array(GalleryUtilities::getServerVar('SERVER_NAME')),
			     unserialize($allow));

	$accessList = array();
	foreach ($allow as $host) {
	    $accessList[] = '%{HTTP_REFERER} !://' . preg_quote($host) . '/ [NC]';
	}

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

    /**
     * Comparison function used to order rewrite rules.
     *
     * @access private
     */
    function _sortRules($a, $b) {
	if (isset($a['settings']['condition']) || isset($b['settings']['condition'])) {
	    if (isset($a['settings']['condition']) && isset($b['settings']['condition'])) {
		$diff = sizeof($a['settings']['condition']) - sizeof($b['settings']['condition']);
		return $diff;
	    }

	    if (isset($a['settings']['condition'])) {
		return -1;
	    }

	    return 1;
	}

	/* Regular expression in both */
	if (strpos($a['urlPattern'], '(') !== false && strpos($b['urlPattern'], '(') !== false) {
	    if (strpos($a['urlPattern'], '(') == strpos($b['urlPattern'], '(')) {
		if (strlen($a['urlPattern']) == strlen($b['urlPattern'])) {
		    return 0;
		}

		return (strlen($a['urlPattern']) < strlen($b['urlPattern'])) ? 1 : -1;
	    }

	    return (strpos($a['urlPattern'], '(') < strpos($b['urlPattern'], '(')) ? 1 : -1;
	}
	if (strpos($a['urlPattern'], '(') !== false) {
	    return 1;
	}
	if (strpos($b['urlPattern'], '(') !== false) {
	    return -1;
	}

	return (strlen($a['urlPattern']) < strlen($b['urlPattern'])) ? 1 : -1;
    }

    /**
     * Replaces all keywords with apropriate rewrite pattern and append to $rewriteRules.
     *
     * @param string url pattern
     * @param array of keywords => regular expresion for the htaccess file
     * @param array of query string params (key => value)
     * @param array of parsed rules
     * @param array of settings ('conditions' => array of RewriteCond's,
     *				 'forbidden' => sets the [F]-flag for a rule).
     * @access private
     */
    function _parseRule($urlPattern, $keywords, $queryString, &$rewriteRules, $settings) {
	$reference = 1;
	preg_match_all('/\%([^\%]+)\%/', $urlPattern, $regs);

	foreach ($regs[1] as $keyword) {
	    if ($keyword == '_path_') {
		list ($ret, $code) = RewriteHelper::_parseRule(str_replace('%_path_%', '%path%',
			$urlPattern), $keywords, $queryString, $rewriteRules, $settings);
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}
		if ($code != REWRITE_STATUS_OK) {
		    return array(GalleryStatus::success(), $code);
		}
		$urlPattern = str_replace('%_path_%/', '', $urlPattern);
		$urlPattern = str_replace('%_path_%', '', $urlPattern);
	    } else {
		if (!isset($keywords[$keyword]['pattern'])) {
		    return array(GalleryStatus::success(), REWRITE_STATUS_BAD_KEYWORD);
		}

		$urlPattern = str_replace('%' . $keyword . '%',
			    $keywords[$keyword]['pattern'], $urlPattern);
	    }

	    if (!isset($keywords[$keyword]['ignore'])) {
		$queryString[$keyword] = '%' . $reference;
	    }
	    $reference++;
	}

	$args = array();
	foreach ($queryString as $key => $value) {
	    $args[] = GalleryUtilities::prefixFormVariable($key) . '=' . $value;
	}

	if (!empty($urlPattern)) {
	    $rewriteRules[] = array(
		    'urlPattern' => $urlPattern,
		    'queryString' => join('&', $args),
		    'settings' => $settings);
	}

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