<?php
/*
 * $RCSfile: SystemChecksStep.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.
 */

class SystemChecksStep extends UpgradeStep {
    function stepName() {
	return _('System Checks');
    }

    /*
     * Returns the exact bytes value from a php.ini setting
     *
     * Copied from PHP.net's manual entry for ini_get()
     */
    function _getBytes($val) {
	$val = trim($val);
	$last = $val{strlen($val)-1};
	switch($last) {
	case 'k':
	case 'K':
	    return (int) $val * 1024;
	    break;
	case 'm':
	case 'M':
	    return (int) $val * 1048576;
	    break;
	default:
	    return $val;
	}
    }

    function processRequest() {
	if (!empty($_GET['zendtest'])) {
	    header("Content-Type: text/plain");
	    header("Content-Length: 8");
	    $x = array(new stdclass());
	    /* v-- This may cause PHP to crash! */
	    $x = $x[0];
	    print "SUCCESS\n";
	    return false;
	}

	return true;
    }

    function loadTemplateData(&$templateData) {
	global $gallery;

	$failCount = 0;

	$suggestedHtaccess = array();

	/* assert compatible version of PHP, we accept 4.1.0+ / 5.0.4+ */
	if (!function_exists('version_compare') || version_compare(phpversion(), '4.1.0', '<') ||
	        (version_compare(phpversion(), '5.0.0', '>=') &&
		       version_compare(phpversion(), '5.0.4', '<'))) {
	    $templateData['check'][] =
		array('title' => _('PHP version >= 4.1.0 or >= 5.0.4'),
		      'error' => true,
		      'notice' => sprintf(_("Error: Gallery 2 requires PHP version 4.1.0 or newer or 5.0.4 or newer. You have PHP version %s installed. Contact your webserver administrator to request an upgrade, available at the %sPHP website%s."), phpversion(), '<a href="http://php.net/">', '</a>'));
	    $failCount++;
	} else {
	    $templateData['check'][] =
		array('title' => _('PHP Version'), 'success' => true);
	}

	/* assert that __FILE__ works correctly */
	if (!SystemChecksStep::CheckFileDirective()) {
	    $templateData['check'][] =
		array('title' => _('FILE directive supported'),
		      'error' => true,
		      'notice' => _('Error: your PHP __FILE__ directive is not functioning correctly. Please file a support request with your webserver administrator or in the Gallery forums.'));
	    $failCount++;
	} else {
	    $templateData['check'][] =
		array('title' => _('FILE Directive'), 'success' => true);
	}

	/* Make sure that safe mode is not enabled */
	if (GalleryUtilities::getPhpIniBool('safe_mode')) {
	    $templateData['check'][] =
		array('title' => _('Safe mode disabled'),
		      'error' => true,
		      'notice' => _('Error: Your version of PHP is configured with safe mode enabled.  You must disable safe mode before Gallery will run.'));
	    $failCount++;
	} else {
	    $templateData['check'][] =
		array('title' => _('Safe Mode'), 'success' => true);
	}

	/* Warning when exec() is disabled */
	if (in_array('exec', split(',\s*', ini_get('disable_functions')))) {
	    $templateData['check'][] =
		array('title' => _('exec() allowed'),
		      'warning' => true,
		      'notice' => _('Warning: The exec() function is disabled in your php by the <b>disabled_functions</b> parameter in php.ini. You won\'t be able to use modules that require external binaries (e.g. ImageMagick, NetPBM or Ffmpeg). This can only be set server-wide, so you will need to change it in the global php.ini.'),
		);
	} else {
	    $templateData['check'][] =
		array('title' => _('exec() allowed'), 'success' => true);
	}

	/* Warning when set_time_limit() is disabled */
	if (in_array('set_time_limit', split(',\s*', ini_get('disable_functions')))) {
	    $timeLimit = ini_get('max_execution_time');
	    $templateData['check'][] =
		array('title' => _('set_time_limit() allowed'),
		      'warning' => true,
		      'notice' => sprintf(_('Warning: The set_time_limit() function is disabled in your php by the <b>disabled_functions</b> parameter in php.ini.  Gallery can function with this setting, but it will not operate reliably.  Any operation that takes longer than %d seconds will fail (and in some cases just return a blank page) possibly leading to data corruption.'), $timeLimit),
		);
	} else {
	    $templateData['check'][] =
		array('title' => _('set_time_limit() allowed'), 'success' => true);
	}

	/* Warning if memory_limit is set and is too low */
	$memoryLimit = ini_get('memory_limit');
	$title = sprintf('%s (%s)', _('Memory limit'), ($memoryLimit == '' ? _('no limit') : $memoryLimit . 'b'));
	$minimumMemoryLimit = 16;
	if ($memoryLimit != '' && ($this->_getBytes($memoryLimit) / (1024 * 1024)) < $minimumMemoryLimit) {
	    $templateData['check'][] =
		array('title' => $title,
		      'warning' => true,
		      'notice' => sprintf(_('Warning: Your PHP is configured to limit the memory to %sb (<b>memory_limit</b> parameter in php.ini). You should raise this limit to at least <b>%sMB</b> for proper Gallery operation.'), $memoryLimit, $minimumMemoryLimit),
		);
	    $suggestedHtaccess[] = sprintf('php_value memory_limit %sM', $minimumMemoryLimit);
	} else {
	    $templateData['check'][] =
		array('title' => $title, 'success' => true);
	}

	/* Warning if file_uploads are not allowed */
	if (! GalleryUtilities::getPhpIniBool('file_uploads')) {
	    $templateData['check'][] =
		array('title' => _('File uploads allowed'),
		      'warning' => true,
		      'notice' =>  _('Warning: Your PHP is configured not to allow file uploads (<b>file_uploads</b> parameter in php.ini). You will need to enable this option if you want to upload files to your Gallery with a web browser.'),
		);
	    $suggestedHtaccess[] = 'php_flag file_uploads on';
	} else {
	    $templateData['check'][] =
		array('title' => _('File uploads allowed'), 'success' => true);
	}

	/* Warning if upload_max_filesize is less than 2M */
	$title = sprintf('%s (%sb)', _('Maximum upload size'), ini_get('upload_max_filesize'));
	$minimumUploadsize = 2;
	$uploadSize = $this->_getBytes(ini_get('upload_max_filesize')) / (1024 * 1024);
	if ($uploadSize < $minimumUploadsize) {
	    $templateData['check'][] =
		array('title' => $title,
		      'warning' => true,
		      'notice' => sprintf(_('Warning: Your PHP is configured to limit the size of file uploads to %sb (<b>upload_max_filesize</b> parameter in php.ini). You should rise this limit to allow uploading bigger files.'), ini_get('upload_max_filesize')),
		);
	    $suggestedHtaccess[] = sprintf('php_value upload_max_filesize %sM', $minimumUploadsize);
	} else {
	    $templateData['check'][] =
		array('title' => $title, 'success' => true);
	}

	/* Warning if post_max_size is less than 2M */
	$title = sprintf('%s (%sb)', _('Maximum POST size'), ini_get('post_max_size'));
	$minimumPostsize = 2;
	if ((int)ini_get('post_max_size') < $minimumPostsize) {
	    $templateData['check'][] =
		array('title' => $title,
		      'warning' => true,
		      'notice' => sprintf(_('Warning: Your PHP is configured to limit the post data to a maximum of %sb (<b>post_max_size</b> parameter in php.ini). You should raise this limit to allow uploading bigger files.'), ini_get('post_max_size')),
		);
	    $suggestedHtaccess[] = sprintf('php_value post_max_size %sM', $minimumPostsize);
	} else {
	    $templateData['check'][] =
		array('title' => $title, 'success' => true);
	}

	/* $x=$x[0] <--(an object) can crash PHP with zend.ze1_compatibility_mode ON */
	if (GalleryUtilities::getPhpIniBool('zend.ze1_compatibility_mode')) {
	    $templateData['check'][] =
		array('title' => _('Zend compatibility mode'),
		      'warning' => true,
		      'notice' => sprintf(_('Warning: Your PHP is configured with Zend ze1_compatibility_mode which can cause PHP to crash.  Click <a href="%s">here</a> to test your PHP.  If you see "SUCCESS" then your PHP is ok.  If you get an error then you must turn off ze1_compatibility_mode before proceeding.'), 'index.php?step=' . $this->_stepNumber . '&amp;zendtest=1'),
		);
	} /* skip showing 'success' for this one */

	/* Check if the files and dirs in the storage dir are (still) writeable */
	$title = _('Storage Directory Permissions');
	if (!SystemChecksStep::CheckFileDirective()) {
	    $templateData['check'][] =
		array('title' => $title,
		      'warning' => true,
		      'notice' => _('Test skipped due to other errors.'));
	} else if (!SystemChecksStep::CheckStorageDirectory()) {
	    $templateData['check'][] =
		array('title' => $title,
		      'error' => true,
		      'notice' =>  sprintf(_('Error: Some files and or directories in your storage directory are not writeable by the webserver user. Run chown -R webserverUser %s OR run chmod -R 777 %s.'),
					   $gallery->getConfig('data.gallery.base'),
					   $gallery->getConfig('data.gallery.base'))
		      );
	    $failCount++;
	} else {
	    $templateData['check'][] =
		array('title' => $title, 'success' => true);
	}

	/* Check all files against MANIFEST */
	$title = _('Gallery file integrity');
	if (!SystemChecksStep::CheckFileDirective()) {
	    $templateData['check'][] =
		array('title' => $title,
		      'warning' => true,
		      'notice' => _('Test skipped due to other errors.'));
	} else {
	    $isCvsInstall = file_exists(dirname(__FILE__) . '/CVS');
	    $manifest = SystemChecksStep::CheckManifest();
	    if (!isset($manifest)) {
		$templateData['check'][] =
		    array('title' => $title,
			  'warning' => true,
			  'notice' => _('Manifest missing or inaccessible.'));
	    } else if (empty($manifest['missing']) && empty($manifest['modified'])
		       && empty($manifest['shouldRemove'])) {
		$templateData['check'][] = array('title' => $title, 'success' => true);
	    } else {
		ob_start();
		include(dirname(__FILE__) . '/../templates/ManifestSystemCheck.html');
		$notice = ob_get_contents();
		ob_end_clean();

		$templateData['check'][] =
		    array('title' => $title,
			  'warning' => true,
			  'notice' => $notice);
	    }
	}

	$templateData['suggestedHtaccess'] = join("\n", $suggestedHtaccess);
	$templateData['bodyFile'] = 'SystemChecks.html';
	$this->setComplete($failCount == 0);
	$this->setInError($failCount > 0);
    }

    function CheckFileDirective() {
	if (strstr(__FILE__, 'upgrade/steps/SystemChecksStep.class') ||
	    strstr(__FILE__, '\\upgrade\\steps\\SystemChecksStep.class')) {
	    return true;
	} else {
	    return false;
	}
    }

    /* Check if the files / dirs in the storage directory are writeable */
    function CheckStorageDirectory() {
	global $gallery;

	$storagePath = $gallery->getConfig('data.gallery.base');
	$platform = $gallery->getPlatform();
	if (empty($storagePath)) {
	    return false;
	} else if ($storagePath{strlen($storagePath)-1} != $platform->getDirectorySeparator()) {
	    $storagePath .= $platform->getDirectorySeparator();
	}

	/* An exhaustive test would take too long, test a few dirs / files */
	foreach (array('.', 'versions.dat', 'albums') as $path) {
	    $path = $storagePath . $path;
	    if (!$platform->file_exists($path) || !$platform->is_readable($path) ||
		    !$platform->is_writeable($path)) {
		return false;
	    }
	}

	/* Check up to 200 other files */
	$tested = 0;
	$max = 200;
	$storagePath = substr($storagePath, 0, strlen($storagePath)-1);
	return SystemChecksStep::_checkStorageSubDirectory($storagePath, $tested, $max);
    }

    /**
     * Check up to $max files / dirs in a directory tree if they are still read/writeable
     *
     * @param string directory
     * @param integer number of already tested files / dirs
     * @param integer max files/dirs to check
     * @return  boolean success
     */
    function _checkStorageSubDirectory($dirname, &$tested, $max) {
	global $gallery;

	$platform = $gallery->getPlatform();

	if (!($fd = $platform->opendir($dirname))) {
	    return false;
	}

	while (($filename = $platform->readdir($fd)) !== false && $tested < $max) {
	    if (!strcmp($filename, '.') || !strcmp($filename, '..')) {
		continue;
	    }

	    $tested++;
	    $path = $dirname . $platform->getDirectorySeparator() . $filename;

	    if (!$platform->is_link($path) &&
		(!$platform->is_writeable($path) || !$platform->is_readable($path))) {
		return false;
	    }

	    if ($platform->is_dir($path) && $tested < $max) {
		/* Threshold not yet reached, check subdirectory tree */
		if (!SystemChecksStep::_checkStorageSubDirectory($path, $tested, $max)) {
		    return false;
		}
	    }
	}
	$platform->closedir($fd);

	return true;
    }

    function CheckManifest() {
	$base = realpath(dirname(__FILE__) . '/../..') . '/';

	$manifest = GalleryUtilities::readManifest();
	if (empty($manifest)) {
	    return null;
	}

	$missing = $modified = $shouldRemove = array();
	foreach ($manifest as $file => $info) {
	    if ($file == 'MANIFEST') {
		continue;
	    }
	    $path = $base . $file;

	    if (!empty($info['removed'])) {
		if (file_exists($path)) {
		    $shouldRemove[] = $file;
		}
	    } else if (!file_exists($path)) {
		$missing[] = $file;
	    } else {
		/*
		 * Use size comparison instead of checksum for speed.  We have
		 * two sizes, one calculated with unix eols, one with windows
		 * eols.
		 */
		$actualSize = filesize($path);
		if ($actualSize != $info['size'] && $actualSize != $info['size_crlf']) {
		    /* This can be useful debug info */
		    if (false) {
			printf("%s (expected: %s/%s, actual: %s)<br/>", $file,
			       $info['size'], $info['size_crlf'], $actualSize);
		    }
		    $modified[] = $file;
		}
	    }
	}

	return array('missing' => $missing, 'modified' => $modified,
		     'shouldRemove' => $shouldRemove);
    }
}
?>
