Forum core source dump (as of July 31)

lib/Core.class.php

/**
* Core class.
* Used for core forum functions, formatting and security.
*
* @author Eksith Rodrigo
* @package Core
* @access public
* @version 0.1
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/

final class Core
{
	static private $instance;	// Singleton instance for this class
	private $cookies;			// Boolean to hold cookie capability (true/false)
	private $props;				// Presets array passed during object construction
	private $db;				// Database object passed during object construction
	private $modules;			// Loaded modules
	
	
	/**
	* Core constructor. 
	* This is a private function. Use "getInstance" below to get a fresh object.
	*
	* @param array $p the presets in the core passed by reference.
	* @param object $d the database object (MySQL, SQLite et al) passed by reference or created just in time.
	* @param object $m loaded modules passed by reference.
	*/
	private function __construct(&$p, &$d, &$m)
	{
		$this->$props = $p;
		
		// If no database object is detected, create one from the type specified in the presets
		if(empty($d))
		{
			// Only has support for MySQL for now. More databases will be supported in later versions
			switch(strtolower($p['db_type']))
			{
				case 'mysql':
					$d = MySQL::getInstance($p['db_host'], $p['db_name'], $p['db_user'], $p['db_pass']);
					break;
			}
		}
		
		$this->$db = $d;
		$this->$modules = $m;
		$this->$cookies = $this->cookiesEnabled();
	}
	
	
	/**
	* Singleton instance.
	*
	* @param array $p The presets in the core passed by reference.
	* @param object $d The database object (MySQL, SQLite et al) passed by reference.
	* @return object Core Singleton instance
	*/
	static function getInstance(&$p, &$d)
	{
		if(!isset(self::$instance))
			self::$instance = new Core($p, $d);
		
		return self::$instance;
	}
	
	
	
	/******************************************************************************
		Posting status and user input
	******************************************************************************/
	
	/**
	* Checks if user is currently browsing the index page.
	*
	* @return bool true If on the index page. Defaults to false.
	*/
	public function browsingIndex()
	{
		if(!isset($_GET['forum']) && !isset($_GET['topic']) && !isset($_GET['page']) && !isset($_GET['section']))
		{
			$modules->__trigger('CoreBrowsingIndex', '', false);
			return true;
		}
			
		return false;
	}
	
	
	/**
	* Checks if user is currently browsing a specific forum.
	*
	* @return bool true If on a forum page. Defaults to false.
	*/
	public function browsingForum()
	{
		if(isset($_GET['forum']) && !isset($_GET['topic']) && !isset($_GET['section']))
		{
			$modules->__trigger('CoreBrowsingForum', array("forum"=>postForumID()), false);
			return true;
		}
		return false;
	}
	
	
	/**
	* Checks if user is currently browsing a specific topic.
	*
	* @return bool true If on a topic page. Defaults to false.
	*/
	public function browsingTopic()
	{
		if(isset($_GET['forum']) && isset($_GET['topic']) && !isset($_GET['section']))
		{
			$modules->__trigger('CoreBrowsingForum', 
				array("forum"=>postForumID(), "topic"=>postTopicID()), false);
			return true;
		}
		return false;
	}
	
	
	/**
	* Checks if user is currently browsing a specific section. I.E. Plugin module.
	*
	* @return bool true If on a section page. Defaults to false.
	*/
	public function browsingSection()
	{
		if(!isset($_GET['forum']) && !isset($_GET['topic']) && isset($_GET['section']))
		{
			$modules->__trigger('CoreBrowsingSection', array("section"=>sectionID()), false);
			return true;
		}
		return false;
	}
	
	
	/**
	* Gets the current page index.
	*
	* @return int $p Filtered output defaults to 1 (first page).
	*/
	public function currentPage()
	{
		$p = 1;
		if(isset($_GET['page']))
			$p = $this->getDefaultInt($_GET['page'], 1);
		
		return $p;
	}
	
	
	/**
	* Status: Posting a new forum topic.
	*
	* @return bool true If $_POST array key "forum" is present and "topic" is absent. Defaults to false.
	*/
	public function postingNewTopic()
	{
		if(isset($_POST['forum']) && !isset($_POST['topic']))
		{
			$modules->__trigger('CorePostingNewTopic', array("forum"=>postForumID()), false);
			return true;
		}
		return false;
	}
	
	
	/**
	* Status: Posting a new topic reply.
	*
	* @return bool true If $_POST array keys "forum" and "topic" are present. Defaults to false.
	*/
	public function postingNewReply()
	{
		if(isset($_POST['forum']) && isset($_POST['topic']))
		{
			$modules->__trigger('CorePostingNewReply', 
				array("forum"=>postForumID(), "topic"=>postTopicID()), false);
			return true;
		}
		return false;
	}
	
	
	/**
	* Status: User is attempting to login.
	*
	* @return bool true If $_POST array keys "username" and "password" are present. Defaults to false.
	*/
	public function checkLogin()
	{
		if(isset($_POST['username']) && isset($_POST['password']))
		{
			$modules->__trigger('CoreLoggingIn', 
				array("username"=>$_POST['username'], "password"=>$_POST['password'])), false);
			return true;
		}
		
		return false;
	}
	
	
	/**
	* Status: User is attempting to login.
	*
	* @return bool true If $_GET array keys "logout" is set and the "user" session is present. Defaults to false.
	*/
	public function checkLogout()
	{
		if(isset($_GET['logout']) && !isset($_GET['login']) && isset($_SESSION['user']))
		{
			$modules->__trigger('CoreLoggingOut', array("user"=>$_SESSION['user']), false);
			return true;
		}
		
		return false;
	}
	
	
	/**
	* Gets the currently requested forum ID. 
	* Priority is given to the $_POST "forum" key.
	*
	* @return int $f_id Filtered output defaults to 0 (no forum requested).
	*/
	public function postForumID()
	{
		$f_id = 0;
		if(isset($_POST['forum']))
			$f_id = $this->getDefaultInt($_POST['forum'], 0);
		elseif(isset($_GET['forum']))
			$f_id = $this->getDefaultInt($_GET['forum'], 0);
		
		return $f_id;
	}
	
	
	/**
	* Gets the currently requested topic ID. 
	* Priority is given to the $_POST "topic" key.
	*
	* @return int $t_id Filtered output defaults to 0 (no topic requested).
	*/
	public function postTopicID()
	{
		$t_id = 0;
		if(isset($_POST['topic']))
			$t_id = $this->getDefaultInt($_POST['topic'], 0);
		elseif(isset($_GET['topic']))
			$t_id = $this->getDefaultInt($_GET['topic'], 0);
		
		return $t_id;
	}
	
	
	/**
	* Gets the currently requested parent (topic or forum) ID. 
	* Priority is given to the $_POST "parent" key.
	*
	* @return int $p_id Filtered output defaults to 0 (no parent item requested).
	*/
	public function postParentID()
	{
		$p_id = 0;
		if(isset($_GET['parent']))
			$p_id = $this->getDefaultInt($_GET['parent'], 0);
		elseif(isset($_GET['parent']))
			$p_id = $this->getDefaultInt($_GET['parent'], 0);
		
		return $p_id;
	}
	
	
	/**
	* Gets the posted subject title form field data.
	*
	* @return string $_t Filtered output defaults to '' (blank string).
	*/
	public function postTitle()
	{
		$_t = '';
		if(isset($_POST['title']))
			$_t = $this->getDefaultString($_POST['title'], '');
		
		return $_t;
	}
	
	
	/**
	* Gets the posted content data (message body) form field data.
	*
	* @return string $_c Filtered output defaults to '' (blank string).
	*/
	public function postContent()
	{
		$_c = '';
		if(isset($_POST['content']))
			$_c = $this->getDefaultHtml($_POST['content'], '');
		
		return $_c;
	}
	
	
	/**
	* Gets the posted author name form field data
	*
	* @return string $_a Filtered output defaults to '' (blank string).
	*/
	public function postAuthor()
	{
		$_a = '';
		if(isset($_POST['author']))
			$_a = $this->getDefaultString($_POST['author'], '');
		
		return $_a;
	}
	
	
	/**
	* Gets the posted author email form field data.
	*
	* @return string $_e Filtered output defaults to '' (blank string).
	*/
	public function postEmail()
	{
		$_e = '';
		if(isset($_POST['email']))
			$_e = $this->getDefaultString($_POST['email'], '');
		
		return $_e;
	}
	
	
	/**
	* Gets a database compatible datetime stamp.
	*
	* @return date Current date time in "Year-Month-Date Hour:Minute:Second" format.
	*/
	public function postDate() {
		return date('Y-m-d H:i:s');
	}
	
	/**
	* Gets the currently requested section ID 
	*
	* @return int $s_id Filtered output defaults to 0 (no section requested).
	*/
	public function sectionID()
	{
		$s_id = 0;
		if(isset($_POST['section']))
			$s_id = $this->getDefaultInt($_POST['section'], 0);
		elseif(isset($_GET['section']))
			$s_id = $this->getDefaultInt($_GET['section'], 0);
		
		return $s_id;
	}
	
	
	/******************************************************************************
		User status and identity
	******************************************************************************/
	
	
	/**
	* Gets user data into variables passed by reference. 
	*
	* @deprecated This function is deprecated in favor of the one with fewer parameters below it 
	* @see function getUserDataArray.
	*
	* @param int $userID user ID key. Defaults to 0 on error
	* @param string $user username. Defaults to '' on error
	* @param array $priv user privileges. Defaults to empty array on error
	* @param int $msgID message ID key. Defaults to 0 on error
	* @param string $exc error message text.
	*/
	public function getUserData(&$userID, &$user, &$priv, &$msgID, &$exc)
	{
		try
		{
			$raw = explode('::', $this->loggedInUser());
			if(is_array($raw)) {
				//list($userIDR, $user, $msgID) = explode(',' $raw[0]);
				/*
				$rr = explode(',' $raw[0]);
				$priv = $this->getPrivileges($raw[1]);
			
				// Filter everything
				$userID = $this->getDefaultInt($rr[0], 0);
				$user = $this->getDefaultString($rr[1], '');
				$msgID = $this->getDefaultInt($rr[2], 0);
				*/
			}
			else
			{
				$userID = 0;
				$user = '';
				$priv = array();
				$msgID = 0;
			}
		}
		catch (Exception $exc)
		{
			$userID = 0;
			$user = '';
			$priv = array();
			$msgID = 0;
		}
	}
	
	
	/**
	* Gets user data as an array.
	*
	* @param array $data user data (id, name, privileges, messageID). Defaults to error message on exception.
	* @return array|string Array with user data if successful or exception message as string on error.
	*/
	public function getUserDataArray()
	{
		$data = null;
		try
		{
			$raw = explode('::', $this->loggedInUser());
			
			// First part of "raw" (user info)
			//list($userID, $user, $msgID) = explode(',' $raw[0]);
			$rr = array();
			
			$rr[0] = 0;
			$rr[1] = '';
			$rr[2] = 0;
			//$rr = explode(',' $raw[0]);
			$data = array(
				"id" => $rr[0],
				"username" => $this->getDefaultString($rr[1], ''),
				"messageID" => $this->getDefaultInt($rr[2], 0),
				"privileges" => $this->getPrivileges($raw[1])	// Second part of "raw" (privileges)
			);
		}
		catch (Exception $exc)
		{
			$data = $exc;
		}
		return $data;
	}
	
	
	/**
	* Saves user data into session and cookies.
	*
	* @deprecated This function is deprecated in favor of the one with fewer parameters below it.
	* @see function setUserDataArray
	*
	* @param int $userID Unique user key
	* @param string $name Username
	* @param array $priv Designated user parameters
	* @param int msgID Display message number
	*/ 
	public function setUserData(&$userID, &$user, &$msgID, &$priv)
	{
		$data = $this->encrypt($userID . ',' . $user . ',' . $msgID . '::' . implode('', $priv));
		
		// Destroy and restart the current session
		session_regenerate_id(true);
		
		$_SESSION['user'] = $data;
		
		if($cookies)
			setcookie("user", $data);
	}
	
	
	/**
	* Saves user data into session and cookie
	*
	* @param array $info Raw user data including id, name, message ID and privileges array
	*/
	public function setUserDataArray(&$info)
	{
		list($userID, $user, $msgID, $priv) = $info;
		
		$data = $this->encrypt($userID . ',' . $user . ',' . $msgID . '::' . implode('', $priv));
		
		// Destroy and restart the current session
		session_regenerate_id(true);
		
		$_SESSION['user'] = $data;
		
		if($cookies)
			setcookie("user", $data);
	}
	
	
	/**
	* Check if user can support cookies
	*
	* @return bool $cookies True if cookies are enabled by the browser, false if else
	*/
	public function cookiesEnabled()
	{
		if(empty($cookies))
		{
			//setcookie("cookies", time());
			if(isset($_COOKIE["cookies"]))
			{
				$cookies = true;
				unset($_COOKIE["cookies"]);
			}
			else
			{
				$cookies = false;
			}
		}
		
		return $cookies;
	}
	
	
	/**
	* Check if user has a valid login session or saved cookie
	*
	* @return string $ret Formatted string pattern containing user ID, name, display name message ID and privilege flags
	*/
	public function loggedInUser()
	{
		// Prevent hijack
		//if(!isset($_SESSION['user']))
		//	session_regenerate_id();
		
		$ret = '0,0,0::000000000000000000000';
		
		if(isset($_SESSION['user']))
			$ret = $this->decrypt($_SESSION['user']);
		elseif(isset($_COOKIE["user"]))
			$ret = $this->decrypt($_COOKIE['user']);
		
		return $ret;	
	}
	
	
	/**
	* Authenticates user based on posted username and password form fields
	*
	* @return bool true If authentication went without problems. Defaults to false.
	*/
	public function login()
	{
		// Make sure the current user is logged out first
		if(isset($_SESSION['user']) || isset($_COOKIE['user']))
			$this->logout();
		
		if(isset($_POST['username']) && isset($_POST['password']))
		{
			$username = $this->getDefaultString($_POST['username'], '');
			$password = $this->getDefaultString($_POST['password'], '');
			
			// Invalid or empty form data
			if(empty($username) || empty($password))
				return false;
			
			// Connect to database and get user by name
			$data = $db->getLoginData($username);
			
			// User doesn't exist
			if(empty($data))
				return false;
			
			// Verify returned authentication data
			$encPass = $this->decrypt($data['pass']);
			$encSalt = $this->decrypt($data['salt']);
			
			// Password matches, login the user and return true
			if(str_replace($encSalt, '', $encPass) == $password)
			{
				$udata = array($data['id'], $username, 0, implode('', $data['priv']));
				$this->setUserDataArray($udata);
				$modules->__trigger('CoreLogin', 
					array("username" => $username, "password" => $password, "privileges" => $data['priv']), 
					false);
				return true;
			}
		}
		
		return false;
	}
	
	
	/**
	* Logout by removing 'user' session and cookie data.
	*/
	public function logout()
	{
		if(isset($_SESSION['user']))
			$_SESSION['user'] = null;
		
		if(isset($_COOKIE['user']))
			unset($_COOKIE['user']);
		
		// Just to be sure
		session_regenerate_id();
	}
	
	
	/**
	* Puts user privileges into an array
	* 
	* @param string $priv User privilegs flags 21 characters in length (0 or 1)
	* @return array $privileges User privileges sorted into readable array
	*/
	public function getPrivileges(&$priv)
	{		
		// Initial array with no privileges
		$privileges = array(
			"CanReply" => 0,
			"CanCreateTopics" => 0,
			"CanUsePM" => 0,
			"CanReplyReadOnlyForums" => 0,
			"CanCreateTopicsReadOnlyForums" => 0,
			"CanEditOwnPosts" => 0,
			"CanDeleteOwnPosts" => 0,
			"CanEditOthersPosts" => 0,
			"CanDeleteOthersPosts" => 0,
			"CanMoveTopics" => 0,
			"CanLockTopics" => 0,
			"CanCreateForums"=> 0,
			"CanEditForums" => 0,
			"CanDeleteForums" => 0,
			"CanCreateUsers" => 0,
			"CanBanUsers" => 0,
			"CanEditUsers" => 0,
			"CanDeleteUsers" => 0,
			"CanCreateGroups" => 0,
			"CanEditGroups" => 0,
			"CanDeleteGroups" => 0
		);
		
		// Get array from privileges string
		$pr = explode('', $priv);
		
		// If the initial array has appropriate number of flags
		if(count($pr) == 21) {
			
			// Get secured values back to privileges from source array
			$this->filterPushToArray("int", $pr, $privileges);
		}
		
		return $privileges;
	}
	
	
	/**
	* Utility function to reset default values in an array with filtered data.
	* Arrays are passed by reference.
	*
	* @param string $type Content type to return. Values "html", "string", "int".
	* @param int $count The number of items to iterate through.
	* @param array $source The raw array containing unfiltered data.
	* @param array $source Sorted destination array with filtered data.
	*/
	public function filterPushToArray($type, $count, &$source, &$dest)
	{
		$type = strtolower($type);
		
		$i = 0;
		
		// Iterate through each key and insert corresponding source array value
		foreach($dest as $key => $value)
		{
			// Get filtered content based on type
			switch($type)
			{
				case "html":
					$dest[$key] = $this->getDefaultHtml($source[i], $value);
					break;
				case "string":
					$dest[$key] = $this->getDefaultString($source[i], $value);
					break;
				case "int":
					$dest[$key] = $this->getDefaultInt($source[i], $value);
					break;
			}
			$i++;
		}
	}
	
	
	
	
	/******************************************************************************
		Formatting and presentation functions
	******************************************************************************/
	
	
	/**
	* Converts topic titles into usable URLs.
	* Will be used along with IDs.
	*
	* @param string $str The raw title
	* @return string $str The cleaned up title with special characters removed
	*/
	public function titleToSlug($str)
	{
		$str = preg_replace('/[~`!\@#\$\%\^\&\*\(\)\-\_\+={}\[\]\|:;\"\'\< \>\?,.\\\/\s+]/imu', '', $str);
		return strtolower($str);
	}
	
	
	/**
	* Check if CAPS are below a percent threshold.
	* This function is for improving readability by enforcing caps limits.
	*
	* @param string $str The posted content
	* @param int $limit The threshold limit in percent
	* @return bool true If the amout of CAPS matches are below the limit, else returns false.
	*/
	public function capsCheck($str, $limit)
	{
		$percent = round(($limit / 100) * strlen($str));
		preg_match_all('/[A-Z]/', $str, $matches);
		return ((count($matches[0]) >= $percent) ? true : false);
	}
	
	
	/**
	* Truncates strings to specified limit and return with ellipse
	* This function is for improving readability by limiting titles or summary lengths.
	*
	* @param string $str The posted content
	* @param int $limit The threshold limit in number of maximum characters. Default is 100.
	* @return string $str Formatted text limited to specified length and followed by '...' .
	*/
	public function stringTrunc($str, $limit = 100)
	{
		if (strlen($str) < = $limit)
			return $str;
		
		return substr_replace($string, '...', ($limit - strlen($str)));
	}
	
	
	/**
	* Pagination HTML.
	* Builds formatted code for templates to display paging links.
	* 
	* @param int $last The final index of items.
	* @param int $total The total number of items
	* @param int $adj The adjacent factor of items to skip. Defaults to 3.
	*/
	public function buildPager($last, $total, $adj=3) {
		$current = $this->currentPage();
		if($current == 0)
			$current = 1;
		
		$prevPage = $current - 1;
		$nextPage = $current + 1;
		$forum_id = $this->postForumID();
		$topic_id = $this->postTopicID();
		$parent_id = $this->postTopicID();
		
		$lastPage = ceil($total / $last);
		$lpm1 = $lastPage - 1;
		$prefix = '';
		if($props['site_friendly_urls']) {
			if($this->browsingTopic())
				$prefix = $props['site_path'] . 'forum$forum_id/topic$topic_id/';
			if($this->browsingForum())
				$prefix = $presets['site_path'] . 'forum$forum_id/';
		}
		else {
			if($this->browsingTopic())
				$prefix = $presets['site_path'] . '?forum=$forum_id&topic=$topic_id&page=';
			if($this->browsingForum())
				$prefix = $presets['site_path'] . '?forum=$forum_id&page=';
		}
		
		$pagination = '';
		if($lastPage > 1) {	
			$pagination .= '<div class="pagination">';
			
			// Previous button
			if ($page > 1) 
				$pagination.= '<a href="$prefix-$prev">⟵ previous</a>';
			else
				$pagination.= '<span class="disabled">⟵ previous</span>';
			
			// "Previous button"
			if ($lastPage < 7 + ($adj * 2))	// Not enough pages to bother breaking it up
			{	
				for ($counter = 1; $counter <= $lastPage; $counter++)
				{
					if ($counter == $page)
						$pagination.= '<span class="current">$counter';
					else
						$pagination.= '<a href="$prefix-$counter">$counter</a>';
				}
			}
			elseif($lastPage > 5 + ($adj * 2))	//There are enough pages to hide some
			{
				// Close to the beginning. Hide later pages
				if($page < 1 + ($adj * 2))		
				{
					for ($counter = 1; $counter < 4 + ($adj * 2); $counter++)
					{
							if ($counter == $page)
							$pagination.= '<span class="current">$counter';
						else
							$pagination.= '<a href="$prefix-$counter">$counter</a>';
					}
					$pagination.= '...';
						$pagination.= '<a href="$prefix-$lpm1">$lpm1</a>';
					$pagination.= '<a href="$prefix-$lastPage">$lastPage</a>';
				}
				// In the middle. Hide earlier pages and later pages
				elseif($lastPage - ($adj * 2) > $page && $page > ($adj * 2))
				{
					$pagination.= '<a href="$prefix">1</a>';
					$pagination.= '<a href="$prefix2">2</a>';
					$pagination.= '...';
					for ($counter = $page - $adj; $counter < = $page + $adj; $counter++)
					{
						if ($counter == $page)
							$pagination.= '<span class="current">$counter';
						else
							$pagination.= '<a href="$prefix-$counter">$counter</a>';
					}
					$pagination.= '...';
					$pagination.= '<a href="$prefix-$lpm1">$lpm1</a>';
					$pagination.= '<a href="$prefix-$lastPage">$lastPage</a>';		
				}
				// Close to the end. Hide earlier pages
				else
				{
					$pagination.= '<a href="$prefix">1</a>';
					$pagination.= '<a href="$prefix-2">2</a>';
					$pagination.= '...';
					for ($counter = $lastPage - (2 + ($adj * 2)); $counter < = $lastPage; $counter++)
					{
						if ($counter == $page)
							$pagination.= '<span class="current">$counter';
						else
							$pagination.= '<a href="$prefix$counter">$counter</a>';					
					}
				}
			}
			
			// Next button
			if ($page < $counter - 1) 
				$pagination.= '<a href="$prefix$next">next ⟶';
			else
				$pagination.= '<span class="disabled">next ⟶</span>';
			$pagination.= "</div>\n";		
		}
	}
	
	
	
		
	/******************************************************************************
		Url authentication and flood limit
	******************************************************************************/
	
	
	/**
	* Verify a certain amount of time has passed between requests
	* Prevents abuse by checking for flood/DoS or forced entry attempts.
	*
	* @deprecated This function is deprecated in favor of a purely database based approach
	*
	* @param int $limit The threshold limit in time format.
	* @return bool true If sufficient time has passed between requests or false if it has not.
	*/
	public function floodLimit($limit)
	{
		$req = $this->postDate();
		
		if(isset($_SESSION['req']) || isset($_COOKIE['req']))
		{
			$sreq = (isset($_SESSION['req']))? strtotime($_SESSION['req']) : $req;
			$creq = (isset($_COOKIE['req']))? strtotime($_COOKIE['req']) : $req;
			
			// Compare and get the most recent time
			if($sreq < $creq)
				$sreq = $creq;
			
			return ($req < ($sreq + strtotime($limit)));
		}
		
		$_SESSION['req'] = $req;
		setcookie('req', $req);
		return false;
	}
	
	/**
	* Authenticate the requested URL as originating from the site homepage
	* Prevents outside redirect attempts by comparing the source and destination URLs (site_url)
	*
	* @param string $req The raw requested URL
	* @return bool true If the source and destination URLs match or false if they dont
	*/
	public function verifyURL($req)
	{
		if(strtolower($req) == substr(0, (strlen(strtolower($props['site_url'])) - 1)))
			return true;
		
		return false;
	}
	
	
	
	
	/*****************************************************************************************************
		Sanitized input
	*****************************************************************************************************/
	
	
	/**
	* Allow only text. HTML escaped if necessary, output unicode.
	* Prevents malicious or accidental HTML tags from being posted. Formatted strings only.
	*
	* @param string $v The content to be checked.
	* @param string $d Default value if $v is empty.
	* @return string $v|$d Verified and formatted content as $v or default, $d, if $v is invalid or empty.
	*/ 
	public function getDefaultString($v, $d)
	{
		// If the content is empty, replace with default values
		$v = (empty($v) && (strtolower($v) != 'false') && ($v != '0'))? $d : $v;
		
		return htmlentities(iconv('UTF-8', 'UTF-8', $v), ENT_QUOTES, 'UTF-8');
	}
	
	/**
	* Allow text with all HTML tags removed.
	* Used for page <title> tags and such where HTML is not allowed
	*
	* @param string $v The content to be checked.
	* @param string $d Default value if $v is empty.
	* @return string $v Filtered plain text value.
	*/
	public function getDefaultText($v, $d)
	{
		// If the content is empty, replace with default values
		$v = (empty($v) && (strtolower($v) != 'false') && ($v != '0'))? $d : $v;
		
		return preg_replace_all("/< [^>]*>/imu", "", $v);
	}
	
	/**
	* Allow only numbers, specifically, integers. No strings and/or HTML allowed.
	* Prevents any value other than an integer from being sent.
	*
	* @param string $v The content to be checked.
	* @param string $d Default value if $v is empty or invalid.
	* @return int $v|$d Verified $v if it is an integer or default, $d, if $v is invalid.
	*/ 
	public function getDefaultInt($v, $d)
	{
		return (!ctype_digit($v))? $d : $v;
	}
	
	
	/**
	* Allow only safe HTML (tags, attributes and attribute values).
	* Prevents any value other than those in the presets whitelists from being sent back.
	* 
	* This function needs more work!
	*
	* @param string $v The content to be checked.
	* @return string $v|$d The filtered and formatted allowed HTML as $v or default fallback value, $d, if $v is empty or cannot be parsed.
	*/
	public function getDefaultHtml($v, $d)
	{
		// Check if the content isn't empty or we skip all the filtering
		$v = (empty($v) && (strtolower($v) != 'false') && ($v != '0'))? $d : $v;
		
		// Content passed empty check
		if($v != $d)
		{
			// Cleaned up output
			$clean = "";
			
			// Get disallowed attribute values from presets
			$badattr = implode('|', $props['ui_format_invalid_attributes']);
			
			/*
			* NOTE: Even though only allowed attributes will be applied on all tags, it's still important to filter out
			* attribute values as well. Internet Explorer, for example, still allows the following to be executed :
			*
			* <span style="onclick:doSomething()">Some text</span>
			*
			* Notice, the "style" attribute, which is normally harmless, in this case is being used to carry out a 
			* JavaScript command
			*/
			
			
			// Get formatting whitelist
			$allowed = implode('|', $props['ui_format_tags']);
			
			// Catch all the HTML tags and attributes
			preg_match_all("!< \s?([0-9a-zA-Z]+)(.*?)(\s?\/)?>(.*?)()?!ismu", $v, $matches);
			
			// HTML tags are present
			if(count($matches[0]) > 0)
			{
				// Iterate through all the allowed tags
				foreach($props['ui_format_tags'] as $tag)
				{
					// Iterate through each match and filter attributes.
					for($i = 0; $i < count($matches[0]); $i++)
					{
						// The current tab matches the iteration
						if($tag[0] == $matches[1][$i])
						{
							// Iterate through each match and filter the attributes.
							for($i = 0; $i < count($matches[0]); $i++)
							{
								// Get the specified attributes for this tag (The first "(.*?)" portion in the above regular expression)
								$attr = explode(' ', $matches[2][$i]);
								
								// Filtered replacement
								$repl = "";
								
								// If there are attributes for this tag
								if(is_array($attr))
								{
									// Iterate through each allowed attribute present in this tag
									foreach($attr as $at)
									{
										// No attributes
										if(!empty($at))
										{
											if(preg_match("/^" . str_replace(',', '|', $tag[1]) . "=\"?[^(" . $badattr . ")]+\"?/ismu", $at))
												$repl .= " " . $at;
										}//if
									}//foreach
								}//if
								
								// Insert the cleaned up tag + attribute
								$clean .= str_replace($matches[0][$i], "<". $matches[1][$i] . $repl . $matches[3][$i] .">", $v);
							}//for
						}//if
					}
				}//foreach
				
				// Remove all tags not in the approved list
				$clean = preg_replace("!< [^(". $allowed .")]>!ismu" , '', $clean);
				
				// Replace original input with cleaned output
				$v = $clean;
			}//if
		}
		
		return $v;
	}
	
	
	
	
	/*****************************************************************************************************
		Security and encryption
	*****************************************************************************************************/
	
	/**
	* Generate a random string with a specific range (for salts etc...)
	*
	* @param int $min The minimum generated string length
	* @param int $max The maximum generated string length
	* @param bool $upper Uppercase (capital) letters are required. Defaults to true.
	* @param bool $special Special characters (punctuation etc...) are required. Defaults to true.
	* @return string $random Generated random string based on specifications.
	*/
	public function genRandom($min, $max, $upper = true, $spec = true)
	{
		$charset = "0123456789abcdefghijklmnopqrstuvwxyz";
		
		if($upper)
			$charset .= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
		
		if($spec)
			$charset .= "~!@#$%^&*()_+`-={}|\\]?[\":;'>< ,./";
		
		$l = mt_rand($min, $max);
		for($i = 0; $i<$l; $i++) {
			$random .= $charset[(mt_rand(0, (strlen($charset)-1)))];
		}
		
		return $random;
	}
	
	/**
	* Encrypt in Rijndael 256 with optional Base64 encoding.
	* If Base64 is used during encryption, it must be used again for decryption.
	*
	* @param string $text The content to be encrypted.
	* @param bool $base64 Optional flag for base64 encoding the the final output. Defaults to true.
	* @return string Encrypted and optionally encoded string
	*/
	public function encrypt($text, $base64=true)
	{
		if($base64)
			return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $salt, $text, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))));
		
		return trim(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $salt, $text, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)));
	}
	
	/**
	* Decrypt as encrypted above in Rijndael 256 with optional Base64 decoding (Default).
	* If Base64 is used to encrypt, it must be used again for decryption.
	*
	* @param string $text The content to be decrypted.
	* @param bool $base64 Optional flag for base64 decoding the input first. Defaults to true.
	* @return string Decrypted content
	*/
	public function decrypt($text, $base64=true)
	{
		if($base64)
			return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $salt, base64_decode($text), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)));
		
		return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $salt, $text, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)));
	}
	
	
	
	
	/*****************************************************************************************************
		Software downloading and updating
	*****************************************************************************************************/
	
	/**
	* Download a specified file to specified destination.
	* Used for updating the software or getting special content from a remote server.
	* If PHP has been compiled with cURL, it will use that. If not, it will try fopen.
	* 
	* @param string $url Download source location
	* @param string $dest Download destination (must be a writable folder)
	* @return bool $ret True If everything went OK or false if something went wrong.
	*/
	public function getFile($url, $dest)
	{
		$ret = false;
		$out = '';
		try
		{
			if(function_exists('curl_init'))
			{
				// If cURL exists, we can use it
				
				$file = curl_init($url);
				curl_setopt($file, CURLOPT_CONNECTTIMEOUT, 3);
				curl_setopt($file, CURLOPT_RETURNTRANSFER, true);
				curl_setopt($file, CURLOPT_BINARYTRANSFER, true);
				
				$out = curl_exec($file);
				
				// After everything is read, close cURL
				curl_close($file);
				
				// Empty if something went wrong
				if(empty($out))
					$ret = false;
				
				$fo = fopen($dest, 'w');
				fwrite($fo, $file);
				fclose($fo);
			}
			else
			{
				// If cURL doesn't exist, we have to stick to fopen
				
				$file = fopen($url, 'rb');
				
				// Buffer the read file
				while(!feof($file))
				{
					$out .= fread($file, 8192);
				}
				
				// Empty if something went wrong
				if(empty($out))
					$ret = false;
				
				fclose($file);
				
				$fo = fopen($dest, 'w');
				fwrite($fo, $out);
				fclose($fo);
			}
			
			// If the newly created file exists, return true
			if(file_exists($dest))
				$ret = true;
		}
		catch(Exception $ex)
		{
			// If something went wrong, return false
			$ret = false;
		}
		
		return $ret;
	}
	
	/**
	* Extract downloaded files to specified directories.
	* Obviously incomplete...
	*/
	public function pkgExtract($file, $dest)
	{
		if(function_exists('zip_open'))
		{
			// Set working path
			$path = $props['site_upload_directory'] . 'tmp';
			
			// Extract the file
			$pkg = zip_open($path, $file);
			
			// Opening successful
			if(is_resource($pkg))
			{
				while($item = zip_read($pkg))
				{
					$cpath = $path . dirname(zip_entry_name($item));
					$cname = $path . zip_entry_name($item);
				}
			}
		}
	}
}

6 thoughts on “Forum core source dump (as of July 31)

  1. Pingback: I have developer ADHD « This page intentionally left ugly

  2. This was a very interesting read for me!
    Although I have a question,
    I am a beginning php programmer, currently developing drupal modules on my internship, but looking to develop some sort of forum script/cms/framework/… . I’m not planning on actually making it public, it’s just a personal project to teach myself some things. So, my question…, I saw in your main.php you do:
    Core::getInstance and Modules::getInstance, is it the same as doing:
    $core = new Core();
    $core->getInstance(…); ? and did you have to define ‘Core’ somewhere or does php automatically recognise it as a class and searches for it (with the autoload function you wrote)?
    thanks in advance!

    You can contact me on my email if you want

    • Hi Jelle

      Yes, the basic idea is to let the autoload function load the class dynamically so you wouldn’t need to declare it elsewhere.

      Technically $core = new Core(); wouldn’t work in this case becuse the __construct function in that class is set to private.

      So you would always have to use the getInstance method when you want to create a new instance unless you’re calling new Core() inside the Core class itself.

  3. Pingback: (Snippet) Blocking IPs from a list file with PHP « This page intentionally left ugly

  4. Pingback: PHP Plugin/Module system | This page intentionally left ugly

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s