<?php
/******************************************************************************
* FileManager.php                                                             *
*******************************************************************************
* SMF: Simple Machines Forum - MOD                                            *
* =========================================================================== *
* Software Version:           2.1                                             *
* Software by:                Aäron van Geffen                                *
* Updates:                    http://www.simplemachines.org                   *
******************************************************************************/

if (!defined('SMF'))
	die('Hacking attempt...');	

function FileManager()
{
	global $protected, $scripturl, $db_prefix, $settings, $modSettings, $txt, $context, $boardurl, $boarddir, $folder;

	// Make this mod only accessible for admins
	isAllowedTo('admin_forum');

	// Load the languagefile
	loadLanguage('FileManager');

	// Some template settings
	$context['page_title'] = $txt['filemanager']['pagetitle'];
	$context['template_layers'][] = 'filemanager';
	loadTemplate('FileManager');
	adminIndex('file_manager');

	// Which folder should we browse?
	$folder = !empty($_REQUEST['folder']) ? $_REQUEST['folder'] : '';

	// Is this folder safe?
	if (strpos($folder, '..') !== false || substr($folder, 0, 1) == '/')
		fatal_error(sprintf($txt['filemanager']['invalid_dir'], $folder), false);
		
	// Change directory to the folder if it exists, else change to $boarddir.
	if (!chdir($boarddir . '/' . $folder))
	{
		chdir($boarddir);
		$folder = '';
	}

	// So, which path are we in?
	$path = $boarddir . (!empty($folder) ? '/' . $folder : '');
		
	// Some files and folders should never be deleted. Let's define a few exceptions for them.
	$protected = array('attachments', 'avatars', 'Packages', 'Smileys', 'Sources', 'Themes', 'Themes/default', 'Themes/default/languages', 'Themes/default/images', 'Themes/default/fonts', 'index.php', 'Settings.php', 'Settings_bak.php');

	// Setup the admin tabs array
	$context['admin_tabs'] = array(
		'title' => $txt['filemanager']['pagetitle'],
		'help' => null,
		'description' => null,
		'tabs' => array(
			'filemanager' => array(
				'title' => $txt['filemanager']['tabindex'],
				'description' => sprintf($txt['filemanager']['mainhelp'], $path),
				'href' => $scripturl . '?action=filemanager' . (!empty($folder) ? ';folder=' . $folder : ''),
				'is_selected' => !isset($_REQUEST['sa']) || in_array((isset($_REQUEST['sa']) ? $_REQUEST['sa'] : ''), array('chmod', 'delete', 'rename')),
			),
			'newfolder' => array(
				'title' => $txt['filemanager']['tabnewfolder'],
				'description' => sprintf($txt['filemanager']['newfolderhelp'], $path),
				'href' => $scripturl . '?action=filemanager;sa=newfolder' . (!empty($folder) ? ';folder=' . $folder : ''),
				'is_selected' => (isset($_REQUEST['sa']) ? $_REQUEST['sa'] : '') == 'newfolder',
			),
			'uploadfile' => array(
				'title' => $txt['filemanager']['tabuploadfile'],
				'description' => sprintf($txt['filemanager']['uploadfilehelp'], $path),
				'href' => $scripturl . '?action=filemanager;sa=uploadfile' . (!empty($folder) ? ';folder=' . $folder : ''),
				'is_selected' => (isset($_REQUEST['sa']) ? $_REQUEST['sa'] : '') == 'uploadfile',
			)
		),
	);

	// Define the allowed subActions
	$subActions = array(
		'index' => 'getIndex',
		'uploadfile' => 'uploadFile',
		'newfolder' => 'newFolder',
		'delete' => 'delete',
		'chmod' => 'chmodFileFolder',
		'rename' => 'renameFileFolder',
		'edit' => 'editFile',
		);
	
	// Default the subaction to the file index
	$context['sub_action'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'index';
	$context['sub_template'] = &$context['sub_action'];
	
	// Call the right function for this subaction.
	$subActions[$context['sub_action']]();
}

// Generate the file index
function getIndex()
{
	global $context, $boardurl, $scripturl, $modSettings, $folder, $files;
  
	// Build the files array
	$dir = dir('.'); 
	while($entry = $dir->read())
	{
		// Skip the link to the current directory
		if ($entry == '.')
			continue;

		// Gather basic information
		$file = array(
			'name' => $entry,
			'is_folder' => is_dir($entry) ? 1 : 0,
			'modified' => timeformat(@filemtime($entry)),
			'permissions' => fileperms($entry),
			'owner' => fileowner($entry),
			'to_parent' => false
		);

		// Is this a folder? Maybe even a link to the parent folder?
		if ($file['is_folder'] && $entry == '..')
		{
			// Strip the last directory from the path
			$pathArr = explode('/', $folder . '/');
			$link = implode('/', array_slice($pathArr, 0, count($pathArr) - 2));
			
			// If there is no folder left after stripping, don't bother adding it to the link
			if (empty($link))
				$file['href'] = $scripturl . '?action=filemanager';
			else
				$file['href'] = $scripturl . '?action=filemanager;folder=' . urlEncode($link);
			
			// This item doesn't have a size
			$file['size'] = '';
			
			// The icon representing the parent directory
			$file['icon'] = 'parent';
			
			// Flag it
			$file['to_parent'] = true;
		}
		
		// Just a regular folder, then?
		elseif ($file['is_folder'])
		{
		  	// Link to the contents of the folder
			$file['href'] = $scripturl . '?action=filemanager;folder=' . urlEncode((!empty($folder) ? $folder . '/' : '') . $file['name']);
			
			// Get the amount of files inside the folder as well
			$file['size'] = getDirSize($entry);

			// The icon representing the directory
			$file['icon'] = 'folder';

			// Command links
			$file['rename_href'] = $scripturl . '?action=filemanager;sa=rename;file=' . urlEncode((!empty($folder) ? $folder . '/' : '') . $file['name']);
			$file['delete_href'] = $scripturl . '?action=filemanager;sa=delete;folder=' . urlEncode((!empty($folder) ? $folder . '/' : '') . $file['name']);

			// We only need a CHMOD link when this isn't a Windows box
			if (empty($_SERVER['WINDIR'])) 
				$file['chmod_href'] = $scripturl . '?action=filemanager;sa=chmod;folder=' . urlEncode((!empty($folder) ? $folder . '/' : '') . $file['name']);
		}

		// Ah, so it's a file.
		else
		{
			// Link to the file
			$file['href'] = $boardurl . '/' . (!empty($folder) ? $folder . '/' : '') . rawurlencode($file['name']);

			// And of course get the file size
			$file['size'] = friendlyFileSize(@filesize($entry));

			// The icon representing a file
			$file['icon'] = 'file';

			// Command links
			$file['edit_href'] = $scripturl . '?action=filemanager;sa=edit;file=' . (!empty($folder) ? $folder . '/' : '') . rawurlencode($file['name']);
			$file['rename_href'] = $scripturl . '?action=filemanager;sa=rename;file=' . (!empty($folder) ? $folder . '/' : '') . rawurlencode($file['name']);
			$file['delete_href'] = $scripturl . '?action=filemanager;sa=delete;file=' . (!empty($folder) ? $folder . '/' : '') . rawurlencode($file['name']);

			// We only need a CHMOD link when this isn't a Windows box
			if (empty($_SERVER['WINDIR'])) 
				$file['chmod_href'] = $scripturl . '?action=filemanager;sa=chmod;file=' . (!empty($folder) ? $folder . '/' : '') . rawurlencode($file['name']);
		}

		$files[] = $file;
	}
	
	// Sort the list of files
	usort($files, 'compare');
	
	// Now find out how much files we've got
	$baseURL = $scripturl . '?action=filemanager;folder=' . (!empty($folder) ? $folder : '');
	$fileStart = !empty($_REQUEST['start']) ? intval($_REQUEST['start']) : 0;	
	$fileCount = count($files);
	$filesPerPage = $modSettings['defaultMaxTopics'];

	// Clean the array and create the page index.
	$files = array_slice($files, $fileStart, $filesPerPage);
	$context['pageIndex'] = constructPageIndex($baseURL, $fileStart, $fileCount, $filesPerPage);
	$context['files'] = $files;
}

// Upload a file
function uploadFile()
{
	global $txt, $context, $boarddir, $folder, $return;

	if (isset($_FILES['uploadfile']))
	{
		// Make sure we got here in a legitimate way
		checkSession('post');

		// New name? Filter illegal characters in it.
		if (!empty($_REQUEST['newname']))
			$filename = str_replace(array('"', '\\', '/', ':', '*', '&', '?', '<', '>', '|', '@', '#', '%', '(', ')', '~', '$', ','), '', $_REQUEST['newname']);
		// No filename set? Use a filtered copy of the original filename.
		if (empty($filename))
			$filename = str_replace(array('"', '\\', '/', ':', '*', '&', '?', '<', '>', '|', '@', '#', '%', '(', ')', '~', '$', ','), '', $_FILES['uploadfile']['name']);

		// Now create a full path
		$filename = $boarddir . '/' . (isset($folder) ? $folder . '/' : '') . $filename;

		// Move the uploaded file to its appropriate location
		if (move_uploaded_file($_FILES['uploadfile']['tmp_name'], $filename))
		{
			@chmod($filename, 0666);
			$return = sprintf($txt['filemanager']['uploadsuccessful'], $filename);
		}
		else
    		$return = $txt['filemanager']['uploadfailed'];
	}
}

// Create a new folder
function newFolder()
{
	global $boarddir, $txt, $folder, $foldername, $return;

	if (isset($_REQUEST['foldername']))
	{
		// Make sure we got here in a legitimate way
		checkSession('post');

		$foldername = str_replace(array('"', '\\', '/', ':', '*', '&', '?', '<', '>', '|', '@', '#', '%', '(', ')', '~', '$', ','), '', $_REQUEST['foldername']);
		
		if (!empty($foldername))
		{
			if (mkdir($boarddir . '/' . $folder . '/' . $foldername))
				$return = $txt['filemanager']['foldercreatedsuccess'];
			elseif (file_exists($boarddir . '/' . $folder . '/' . $foldername))
				$return = $txt['filemanager']['folderalreadyexists'];
			else
				$return = $txt['filemanager']['foldererrorother'];
		}
		else
		  	$return = $txt['filemanager']['folderinvalid'];
	}
}

// Create or rename a file or folder
function renameFileFolder()
{
	global $txt, $protected, $return;
		
	// Is this file or folder protected? If so, show a note, but don't log it.
	if (in_array($_REQUEST['file'], $protected))
		fatal_error(sprintf($txt['filemanager']['protected'], $_REQUEST['file']), false);
	
	// We've passed the test, do the renaming if applicable.
	if (isset($_REQUEST['newname']) && isset($_REQUEST['oldname']))
	{
		// Make sure we got here in a legitimate way
		checkSession('post');

		if (rename($_REQUEST['oldname'], $_REQUEST['newname']))
			$return = $txt['filemanager']['renamesuccess'];
		else
			$return = $txt['filemanager']['renamefailed'];
	}
}

// Edit a file
function editFile()
{
	global $txt, $protected, $return, $boarddir, $writable, $file, $contents;
	
	// Determine the filename
	if (!isset($_REQUEST['file']))
		fatal_error($txt['filemanager']['editnofile'], false);
	elseif (isset($_REQUEST['folder']))
		$file = $boarddir . '/' . $_REQUEST['folder'] . '/' . $_REQUEST['file'];
	else
		$file = $boarddir . '/' . $_REQUEST['file'];
	
	// Does this file actually exist?
	$exists = file_exists($file);
	if (!$exists)
		fatal_error($txt['filemanager']['editnofile'], false);
	
	// Is this file writable?
	$writable = is_writable($file);
	
	// Is this file or folder protected? If so, show a note, but don't log it.
	if (in_array($_REQUEST['file'], $protected))
		fatal_error(sprintf($txt['filemanager']['protected'], $_REQUEST['file']), false);

	// Save the file when there's something submitted
	if ($exists && $writable && isset($_POST['content']))
	{
		// Make sure we got here in a legitimate way
		checkSession('post');
		
		// Make sure we get rid of added slashes
		$newContent = stripslashes($_POST['content']);
			
		// Save it!
		$open = fopen($file, 'w');
		$done = fwrite($open, $newContent);
		fclose($open);
		
		// Show the user whether we succeeded or not
		if ($done !== false)
			$return = sprintf($txt['filemanager']['editsavesuccess'], $file);
		else
			$return = sprintf($txt['filemanager']['editsavefailed'], $file);
	}
	// Get the file's current contents
	elseif ($exists && $writable)
	{
		$contents = htmlentities(file_get_contents($file));
	}
}

// CHMOD a file or folder
function chmodFileFolder()
{
	global $txt, $return;
		
	// If we've entered a new permission value, convert it to octals
	$permissions = isset($_REQUEST['newperms']) ? octdec($_REQUEST['newperms']) : 0;
	
	if ($permissions != 0)
	{
		// Make sure we got here in a legitimate way
		checkSession('post');
			 	
		if (chmod($_REQUEST['what'], $permissions))
			$return = $txt['filemanager']['chmodsuccess'];
		else
			$return = $txt['filemanager']['chmodfailure'];
	}
}

// Delete a file or folder
function delete()
{
	global $boarddir, $protected, $txt, $return;

	// What are we attempting to delete?
	if (isset($_REQUEST['folder']))
		$deletewhat = $_REQUEST['folder'];
	elseif (isset($_REQUEST['file']))
		$deletewhat = $_REQUEST['file'];
	
	// Is this file protected? If so, show a note, but don't log it.
	if (in_array($deletewhat, $protected))
		fatal_error(sprintf($txt['filemanager']['protected'], $deletewhat), false);
	
	// Okay, delete it, but only if we're really sure!
	if (isset($_REQUEST['sure']))
	{
		// Make sure we got here in a legitimate way
		checkSession('post');
		
		// Deleting a folder, are we?
		if (isset($_REQUEST['folder']))
			deletefolder($boarddir . '/' . $_REQUEST['folder']);

		// No? A file then?
		elseif (isset($_REQUEST['file']))
		{
			$deletewhat = $boarddir . '/' . $_REQUEST['file'];
			// Does it actually exist?
			if (file_exists($deletewhat))
			{ 
				// Can we delete it, and are we sure it's not a dir?
				if (is_writable($deletewhat) && !is_dir($deletewhat))
				{
					@unlink($deletewhat);
					$return = sprintf($txt['filemanager']['deletesuccessful'], $deletewhat);
				}
				// We can't delete it.
				else
					$return = sprintf($txt['filemanager']['deletefailure'], $deletewhat);
			}
			// It doesn't even exist! The agony!
			else
				$return = sprintf($txt['filemanager']['deletedoesntexist'], $deletewhat);
		}
	}
}

// ===============================
//   MINOR, SUPPORTING FUNCTIONS
// ===============================

// Sorts the names of files and folders
function compare($entry1, $entry2)
{
	// Is entry 1 a parent folder?
	if ($entry1['to_parent'])
		return -1;

	// Is entry 2 a parent folder?
	if ($entry2['to_parent'])
		return 1;

	// Folders should always be at the top
	if ($entry1['is_folder'] != $entry2['is_folder'])
		return $entry2['is_folder'] - $entry1['is_folder'];

	// Still here? Then compare the names of the entries
	return strcoll(strtolower($entry1['name']), strtolower($entry2['name']));
}

// Create a more user friendly filesize
function friendlyFileSize($size)
{
	static $sizeUnits = array();
	if (count($sizeUnits) == 0)
	{
		$sizeUnits[] = 'bytes';
		$sizeUnits[] = 'kB';
		$sizeUnits[] = 'MB';
		$sizeUnits[] = 'GB';
		$sizeUnits[] = 'TB';
	}
	
	if ($size === '')
		return '';
	
	$unitIndex = 0;
	while ($size > 1024.0)
	{
		$size = $size / 1024.0;
		$unitIndex ++;
	}
	
	if ($unitIndex == 0)
		return comma_format($size, 0) . ' ' . $sizeUnits[$unitIndex];
	else
		return comma_format($size, 1) . ' ' . $sizeUnits[$unitIndex];
}

// Get the size of a directory
function getDirSize($dirname)
{
	global $txt;

	$dir = dir($dirname);
	$fileCount = 0;
	while ($filename = $dir->read())
		$fileCount++;

	// We don't want to count [.] and [..]
	return ($fileCount - 2) . ' ' . ($fileCount != 1 ? $txt['filemanager']['items'] : $txt['filemanager']['item']);
}

// Deletes a folder
function deleteFolder($path)
{
	global $txt, $return;

	// First we'll have to delete the folder
	if (emptyFolder($path))
	{
		if (@rmdir($path))
		{
			// Deleted the folder succesfully
			$return .= '<b>' . sprintf($txt['filemanager']['delfolder']['mapverwijderd'], $path) . '</b><br>';
		}
		else
		{
			// Folder couldn't be deleted :(
			$return .= '<b>' . sprintf($txt['filemanager']['delfolder']['mapverwfout'], $path) . '</b><br>';
		}
	}
	else
	{
		// Folder isn't empty, and thus cannot be deleted
		$return .= '<b>' . sprintf($txt['filemanager']['delfolder']['mapnietleeg'], $path) . '</b><br>';
	}
}

// Emptys a folder completely (help function of deletefolder())
function emptyFolder($path)
{
	global $return, $txt;

	// Show we're starting
	$return .= '
				<b>' . sprintf($txt['filemanager']['delfolder']['beginlegen'], $path) . '</b><br />';

	// Let's truncate this folder!
	if ($dir_handle = @opendir($path))
	{
		$return .= '
				<ul>';
		
		while ($file = @readdir($dir_handle))
		{
			// Delete file
			if ($file != '.' && $file != '..' && !is_dir($path . '/' . $file))
			{
				$return .= '
					<li>' . sprintf($txt['filemanager']['delfolder']['verwbestand'], $path . '/' . $file) . '</li>';

				@unlink($path . '/' . $file);
			}
			// Delete folder
			elseif ($file != '.' && $file != '..' && is_dir($path . '/' . $file))
			{
				$return .= '
					<li>' . sprintf($txt['filemanager']['delfolder']['submapgevonden'], $path . '/' . $file) . '<br />';
				
				deleteFolder($path . '/' . $file);
				
				$return .= '
					</li>';
			}
		}
		@closedir($dir_handle);
		$return .= '
				</ul>
				<b>' . sprintf($txt['filemanager']['delfolder']['legenklaar'], $path) . '</b><br />';

		// All files were deleted successfully.
		return true;
	}
	else
	{
		// Folder doesn't exist? What the..?
		$return .= '
				<b>' . $txt['filemanager']['delfolder']['bestaatniet'] . '</b><br />';
		return false;
	}   
}
?>