<?php
/** @file
 * The main "controller" file of ViewGit.
 *
 * All requests come to this file. You can think of it as the controller in the
 * Model-View-Controller pattern. It reads config, processes user input,
 * fetches required data using git commandline, and finally passes the data to
 * templates to be shown to the user.
 */
error_reporting(E_ALL | E_STRICT);

require_once('inc/config.php');
require_once('inc/functions.php');
require_once('inc/plugins.php');

// Include all plugins
foreach (glob('plugins/*/main.php') as $plugin) {
	require_once($plugin);

	$parts = explode('/', $plugin);
	$name = $parts[1];

	$classname = "${name}plugin";
	$inst = new $classname;
}

$old_error_handler = set_error_handler('vg_error_handler');

// Adjust error_reporting based on config.
if (!$conf['debug']) {
	error_reporting(E_ALL ^ E_NOTICE);
}

if (isset($conf['auth_lib'])){
	require_once("inc/auth_{$conf['auth_lib']}.php");
	auth_check();
}

if (isset($conf['projects_glob'])) {
	foreach ($conf['projects_glob'] as $glob) {
		foreach (glob($glob) as $path) {
			// Get the last part of the path before .git
			$name = preg_replace(array('#/?\.git$#', '#^.*/#'), array('', ''), $path);

			// Workaround against name collisions; proj, proj1, proj2, ...
			$i = '';
			while (in_array($name . $i, array_keys($conf['projects']))) {
				@$i++;
			}
			$name = $name . $i;
			$conf['projects'][$name] = array('repo' => $path);
		}
	}
}

$action = 'index';
$template = 'index';
$page['title'] = 'ViewGit';

if (isset($_REQUEST['a'])) {
	$action = strtolower($_REQUEST['a']);
}
$page['action'] = $action;

/*
 * index - list of projects
 */
if ($action === 'index') {
	$template = 'index';
	$page['title'] = 'List of projects - ViewGit';

	foreach (array_keys($conf['projects']) as $p) {
		$page['projects'][] = get_project_info($p);
	}
}

/*
 * archive - send a tree as an archive to client
 * @param p project
 * @param h tree hash
 * @param t type, "targz" or "zip"
 * @param n OPTIONAL name suggestion
 */
elseif ($action === 'archive') {
	$project = validate_project($_REQUEST['p']);
	$tree = validate_hash($_REQUEST['h']);
	$type = $_REQUEST['t'];

	$basename = "$project-tree-". substr($tree, 0, 7);
	if (isset($_REQUEST['n'])) {
		$basename = "$project-$_REQUEST[n]-". substr($tree, 0, 6);
	}

	if ($type === 'targz') {
		header("Content-Type: application/x-tar-gz");
		header("Content-Transfer-Encoding: binary");
		header("Content-Disposition: attachment; filename=\"$basename.tar.gz\";");
		run_git_passthru($project, "archive --format=tar $tree |gzip");
	}
	elseif ($type === 'zip') {
		header("Content-Type: application/x-zip");
		header("Content-Transfer-Encoding: binary");
		header("Content-Disposition: attachment; filename=\"$basename.zip\";");
		run_git_passthru($project, "archive --format=zip $tree");
	}
	else {
		die('Invalid archive type requested');
	}

	die();
}

/*
 * blob - send a blob to browser with filename suggestion
 * @param p project
 * @param h blob hash
 * @param n filename
 */
elseif ($action === 'blob') {
	$project = validate_project($_REQUEST['p']);
	$hash = validate_hash($_REQUEST['h']);
	$name = $_REQUEST['n'];

	header('Content-type: application/octet-stream');
	header("Content-Disposition: attachment; filename=$name"); // FIXME needs quotation

	run_git_passthru($project, "cat-file blob $hash");
	die();
}

/*
 * co - git checkout. These requests come from mod_rewrite, see the .htaccess file.
 * @param p project
 * @param r path
 */
elseif ($action === 'co') {
	if (!$conf['allow_checkout']) { die('Checkout not allowed'); }

	// For debugging
	debug("Project: $_REQUEST[p] Request: $_REQUEST[r]");

	// eg. info/refs, HEAD
	$p = validate_project($_REQUEST['p']); // project
	$r = $_REQUEST['r']; // path

	$gitdir = $conf['projects'][$p]['repo'];
	$filename = $gitdir .'/'. $r;

	// make sure the request is legit (no reading of other files besides those under git projects)
	if ($r === 'HEAD' || $r === 'info/refs' || preg_match('!^objects/info/(packs|http-alternates|alternates)$!', $r) > 0 || preg_match('!^objects/[0-9a-f]{2}/[0-9a-f]{38}$!', $r) > 0) {
		if (file_exists($filename)) {
			debug('OK, sending');
			readfile($filename);
		} else {
			debug('Not found');
			header('HTTP/1.0 404 Not Found');
		}
	} else {
		debug("Denied");
	}

	die();
}

/*
 * commit - view commit information
 * @param p project
 * @param h commit hash
 */
elseif ($action === 'commit') {
	$template = 'commit';
	$page['project'] = validate_project($_REQUEST['p']);
	$page['title'] = "$page[project] - Commit - ViewGit";
	$page['commit_id'] = validate_hash($_REQUEST['h']);
	$page['subtitle'] = "Commit ". substr($page['commit_id'], 0, 6);

	$info = git_get_commit_info($page['project'], $page['commit_id']);

	$page['author_name'] = $info['author_name'];
	$page['author_mail'] = $info['author_mail'];
	$page['author_datetime'] = gmstrftime($conf['datetime_full'], $info['author_utcstamp']);
	$page['author_datetime_local'] = gmstrftime($conf['datetime_full'], $info['author_stamp']) .' '. $info['author_timezone'];
	$page['committer_name'] = $info['committer_name'];
	$page['committer_mail'] = $info['committer_mail'];
	$page['committer_datetime'] = gmstrftime($conf['datetime_full'], $info['committer_utcstamp']);
	$page['committer_datetime_local'] = gmstrftime($conf['datetime_full'], $info['committer_stamp']) .' '. $info['committer_timezone'];
	$page['tree_id'] = $info['tree'];
	$page['parents'] = $info['parents'];
	$page['message'] = $info['message'];
	$page['message_firstline'] = $info['message_firstline'];
	$page['message_full'] = $info['message_full'];

}

/*
 * commitdiff - view diff of a commit
 * @param p project
 * @param h commit hash
 */
elseif ($action === 'commitdiff') {
	$template = 'commitdiff';
	$page['project'] = validate_project($_REQUEST['p']);
	$page['title'] = "$page[project] - Commitdiff - ViewGit";
	$hash = validate_hash($_REQUEST['h']);
	$page['commit_id'] = $hash;
	$page['subtitle'] = "Commitdiff ". substr($page['commit_id'], 0, 6);

	$info = git_get_commit_info($page['project'], $hash);

	$page['tree_id'] = $info['tree'];

	$page['message'] = $info['message'];
	$page['message_firstline'] = $info['message_firstline'];
	$page['message_full'] = $info['message_full'];
	$page['author_name'] = $info['author_name'];
	$page['author_mail'] = $info['author_mail'];
	$page['author_datetime'] = gmstrftime($conf['datetime'], $info['author_utcstamp']);

	$text = git_diff($page['project'], "$hash^", $hash);
	list($page['files'], $page['diffdata']) = format_diff($text);
	//$page['diffdata'] = format_diff($text);
}

elseif ($action === 'patch') {
	$project = validate_project($_REQUEST['p']);
	$hash = validate_hash($_REQUEST['h']);
	$filename = "$project-". substr($hash, 0, 7) .".patch";

	//header("Content-Type: text/x-diff");
	header("Content-Type: application/octet-stream");
	header("Content-Transfer-Encoding: binary");
	// TODO git-style filename
	header("Content-Disposition: attachment; filename=\"$filename\";");

	run_git_passthru($project, "format-patch --stdout $hash^..$hash");
	die();
}

/*
 * rss-log - RSS feed of project changes
 * @param p project
 */
elseif ($action === 'rss-log') {
	$page['project'] = validate_project($_REQUEST['p']);

	$ext_url = 'http://'. $_SERVER['HTTP_HOST'] . dirname($_SERVER['SCRIPT_NAME']) .'/';

	$page['rss_title'] = "Log for $page[project]";
	$page['rss_link'] = $ext_url . makelink(array('a' => 'summary', 'p' => $page['project']));
	$page['rss_description'] = "Git log for project $page[project], generated by ViewGit.";
	$page['rss_pubDate'] = rss_pubdate(time());
	$page['rss_ttl'] = $conf['rss_ttl'];

	$page['rss_items'] = array();

	$diffstat = strstr($conf['rss_item_description'], '{DIFFSTAT}');

	$revs = git_get_rev_list($page['project'], $conf['rss_max_items']);
	foreach ($revs as $rev) {
		$info = git_get_commit_info($page['project'], $rev);
		$link = $ext_url . makelink(array('a' => 'commit', 'p' => $page['project'], 'h' => $rev));
		if ($diffstat) {
			$info['diffstat'] = git_diffstat($page['project'], $rev);
		}

		$page['rss_items'][] = array(
			'title' => rss_item_format($conf['rss_item_title'], $info),
			'guid' => $link,
			'link' => $link,
			'description' => rss_item_format($conf['rss_item_description'], $info),
			'pubdate' => rss_pubdate($info['author_utcstamp']),
		);
	}

	require('templates/rss.php');
	die();
}

/*
 * search - search project history
 * @param p project
 * @param st search type: commit,grep,author,committer,pickaxe
 * @param s string to search for
 */
elseif ($action === 'search') {
	$template = 'shortlog';

	$page['project'] = validate_project($_REQUEST['p']);

	$info = git_get_commit_info($page['project']);
	$page['commit_id'] = $info['h'];
	$page['tree_id'] = $info['tree'];

	$type = $_REQUEST['st'];
	$string = $_REQUEST['s'];

	$page['search_t'] = $type;
	$page['search_s'] = $string;

	$commits = git_search_commits($page['project'], $type, $string);
	$shortlog = array();
	foreach ($commits as $c) {
		$info = git_get_commit_info($page['project'], $c);
		$shortlog[] = array(
			'author' => $info['author_name'],
			'date' => gmstrftime($conf['datetime'], $info['author_utcstamp']),
			'message' => $info['message'],
			'commit_id' => $info['h'],
			'tree' => $info['tree'],
			'refs' => array(),
		);
	}
	$page['shortlog'] = $shortlog;
}

/*
 * shortlog - project shortlog entries
 * @param p project
 * @param h OPTIONAL commit id to start showing log from
 */
elseif ($action === 'shortlog') {
	$template = 'shortlog';
	$page['project'] = validate_project($_REQUEST['p']);
	$page['title'] = "$page[project] - Shortlog - ViewGit";
	$page['subtitle'] = "Shortlog";
	if (isset($_REQUEST['h'])) {
		$page['ref'] = validate_hash($_REQUEST['h']);
	} else {
		$page['ref'] = 'HEAD';
	}

	$info = git_get_commit_info($page['project'], $page['ref']);
	$page['commit_id'] = $info['h'];
	$page['tree_id'] = $info['tree'];

	$page['shortlog'] = handle_shortlog($page['project'], $page['ref']);
}
elseif ($action === 'summary') {
	$template = 'summary';
	$page['project'] = validate_project($_REQUEST['p']);
	$page['title'] = "$page[project] - Summary - ViewGit";
	$page['subtitle'] = "Summary";

	$info = git_get_commit_info($page['project']);
	$page['commit_id'] = $info['h'];
	$page['tree_id'] = $info['tree'];

	$page['shortlog'] = handle_shortlog($page['project']);

	$page['tags'] = handle_tags($page['project'], $conf['summary_tags']);

	$heads = git_get_heads($page['project']);
	$page['heads'] = array();
	foreach ($heads as $h) {
		$info = git_get_commit_info($page['project'], $h['h']);
		$page['heads'][] = array(
			'date' => gmstrftime($conf['datetime'], $info['author_utcstamp']),
			'h' => $h['h'],
			'fullname' => $h['fullname'],
			'name' => $h['name'],
		);
	}
}
elseif ($action === 'tags') {
	$template = 'tags';
	$page['project'] = validate_project($_REQUEST['p']);
	$page['title'] = "$page[project] - Tags - ViewGit";

	$info = git_get_commit_info($page['project']);
	$page['commit_id'] = $info['h'];
	$page['tree_id'] = $info['tree'];

	$page['tags'] = handle_tags($page['project']);
}
/*
 * Shows a tree, with list of directories/files, links to them and download
 * links to archives.
 *
 * @param p project
 * @param h tree hash
 * @param hb OPTIONAL base commit (trees can be part of multiple commits, this
 * one denotes which commit the user navigated from)
 * @param f OPTIONAL path the user has followed to view this tree
 */
elseif ($action === 'tree') {
	$template = 'tree';
	$page['project'] = validate_project($_REQUEST['p']);
	if (isset($_REQUEST['h'])) {
		$page['tree_id'] = validate_hash($_REQUEST['h']);
	}
	/*
	else {
		// TODO walk the tree
		$page['tree_id'] = 'HEAD';
	}
	*/
	$page['title'] = "$page[project] - Tree - ViewGit";

	// 'hb' optionally contains the commit_id this tree is related to
	if (isset($_REQUEST['hb'])) {
		$page['commit_id'] = validate_hash($_REQUEST['hb']);
	}
	else {
		// for the header
		$info = git_get_commit_info($page['project']);
		$page['commit_id'] = $info['h'];
	}

	$page['path'] = '';
	if (isset($_REQUEST['f'])) {
		$page['path'] = $_REQUEST['f']; // TODO validate?
	}

	// get path info for the header
	$page['pathinfo'] = git_get_path_info($page['project'], $page['commit_id'], $page['path']);
	if (!isset($page['tree_id'])) {
		// Take the last hash from the tree
		if (count($page['pathinfo']) > 0) {
			$page['tree_id'] = $page['pathinfo'][count($page['pathinfo']) - 1]['hash'];
		} else {
			$page['tree_id'] = 'HEAD';
		}
	}

	$page['subtitle'] = "Tree ". substr($page['tree_id'], 0, 6);
	$page['entries'] = git_ls_tree($page['project'], $page['tree_id']);
}
/*
 * View a blob as inline, embedded on the page.
 * @param p project
 * @param h blob hash
 * @param hb OPTIONAL base commit
 */
elseif ($action === 'viewblob') {
	$template = 'blob';
	$page['project'] = validate_project($_REQUEST['p']);
	$page['hash'] = validate_hash($_REQUEST['h']);
	$page['title'] = "$page[project] - Blob - ViewGit";
	if (isset($_REQUEST['hb'])) {
		$page['commit_id'] = validate_hash($_REQUEST['hb']);
	}
	else {
		$page['commit_id'] = 'HEAD';
	}
	$page['subtitle'] = "Blob ". substr($page['hash'], 0, 6);

	$page['path'] = '';
	if (isset($_REQUEST['f'])) {
		$page['path'] = $_REQUEST['f']; // TODO validate?
	}

	// For the header's pagenav
	$info = git_get_commit_info($page['project'], $page['commit_id']);
	$page['commit_id'] = $info['h'];
	$page['tree_id'] = $info['tree'];

	$page['pathinfo'] = git_get_path_info($page['project'], $page['commit_id'], $page['path']);

	$page['data'] = join("\n", run_git($page['project'], "cat-file blob $page[hash]"));

	// GeSHi support
	if ($conf['geshi'] && strpos($page['path'], '.')) {
		$old_mask = error_reporting(E_ALL ^ E_NOTICE);
		require_once($conf['geshi_path']);
		$ext = array_pop(explode('.', $page['path']));
		$lang = Geshi::get_language_name_from_extension($ext);
		if (strlen($lang) > 0) {
			$geshi =& new Geshi($page['data'], $lang);
			if (is_int($conf['geshi_line_numbers'])) {
				if ($conf['geshi_line_numbers'] == 0) {
					$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
				}
				else {
					$geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS, $conf['geshi_line_numbers']);
				}
			}
			$page['html_data'] = $geshi->parse_code();
		}
		error_reporting($old_mask);
	}
}
elseif (in_array($action, array_keys(VGPlugin::$plugin_actions))) {
	VGPlugin::$plugin_actions[$action]->action($action);
	die();
}
else {
	die('Invalid action');
}

require 'templates/header.php';
require "templates/$template.php";
require 'templates/footer.php';