<file_sep><div id='profileArea'>
<?php
	// boldShow($type, $content) returns escaped html with $type in bold followed by
	//							 $content in normal style.
	//							 If the profile is a form, boldShow will turn it into
	//							 an input field.
	function boldShow($type, $content, $profileType = "", $textblock = false)
	{	
		
		$type = htmlentities($type, ENT_QUOTES, "UTF-8");
		$content = htmlentities($content, ENT_QUOTES, "UTF-8");
		return "<p class='profiledata'><b>$type:</b> $content</p>\n";
	}
	
	// Calculates the current age based on the birthdate
	function getAge($birthdate)
	{
		list($year, $month, $day) = explode("-", $birthdate);
		$year_diff  = date("Y") - $year;
    	$month_diff = date("m") - $month;
    	$day_diff   = date("d") - $day;
    	
    	// If the month-difference is smaller then 0, or is 0 with a smaller day-difference,
    	// the user hasn't had his/her birthday this year yet.
    	if($month_diff < 0) {
    		$year_diff--;
    	}
    	else if($month_diff == 0 && $day_diff < 0) {
    		$year_diff--;
    	}
    	return $year_diff;
	}
	
	function getLikePicture(array $likestatus)
	{
		$status = array();
		foreach($likestatus as $liked) {
			if($liked) {
				$status[] = "liked";
			}
			else {
				$status[] = "pending";
			}
		}
		return '<img class="likepicture" src="' . base_url() . "img/$status[0]-$status[1].jpg"
		        . '" width="90" height="60" />';
	}

	foreach($profiles as $profile) {
		
		
		// A profilebox is opened and the username and gender for the public profile are echoed.
		// If the profile is big, the profilebox is a section.
		if($profileType == "big") {
			$boxType = "<div id='bigprofile' ";
		}
		// If the profile is small, it is a link
		else {
			$boxType = "<a href=\"". base_url(). 
				"/index.php/viewprofile/showprofile/". $profile['userId']. "\" ";
		}
		
		// The gender is converted to readable matter
		if($profile['gender'] == '0') {
			$gender = "M";
		}
		else {
			$gender = "F";
		}
		// The username and gender are displayed extra big
		echo $boxType. "class='profilebox'>\n". heading(htmlentities($profile['username']. " (".
				 $gender. ")", ENT_QUOTES, "UTF-8"), 3);
		
		$loggedIn = $this->authentication->userLoggedIn();
		$imgfile;
		if($loggedIn && $profile['picture'])
		{
		    // The file name does not depend on user input and therefore does not need to be 
		    // escaped. 
		    $imgfile = 'pictures/' . $profile['picture'];
		}
		else
		{
		    $imgfile = 'silhouette-' . ($profile['gender'] == 0 ? 'man' : 'woman') . '.jpg';
		}
		
		// Show thumbnail.
		$imgurl = base_url() . 'img/' . $imgfile;
		echo '<img class="profilepicture" src="' . $imgurl . '" alt="Profile photo" width="125" height="150" />';
		
		// The thumbnail is shown. If the user is logged in, the real picture, else, a silhouette.
		if($loggedIn) 
		{
			// If there is a mutual like, show the users name
			if(isset($profile['likestatus']) && $profile['likestatus'][0] && $profile['likestatus'][1]) {
				echo boldShow("Name", $profile['firstName']. " ". $profile['lastName']);
				echo boldShow("Email", $profile['email']);
			}
		} 
		
		// The age is displayed
		echo boldShow("Age", getAge($profile['birthdate']), $profileType);
		
		// If the profileType is small, the discription should show only the first line.
		if($profileType == "small") {
			$point = strpos($profile['description'], '.');		// Lines usually end with a point.
			
			// However, if the line is longer then 100 characters, it is cut off.
			if($point === false or $point > 100) {
				$disc = substr($profile['description'], 0, 100). "...";
			}
			else {
				$disc = substr($profile['description'], 0, $point+1);
			}
		}
		else {
			$disc = $profile['description'];
		}
		
		// And the discription, in correct length, is displayed.
		// The true turns it into a textarea if the profile is a form.
		echo boldShow("About me", $disc, $profileType, true);
		
		// The usertype and preference are displayed
		echo boldShow("Personality", $profile['personality']);
		echo boldShow("Preference", $profile['preference']);
		
		// If the profile is small, only a maximum of 4 random brands are shown
		if($profileType == 'small') {
			$maxBrands = min(array(3, count($profile['brands']) - 1));
			shuffle($profile['brands']);
		}
		// else, if the profile is big, all brands are shown
		else $maxBrands = count($profile['brands']) - 1;
		
		$brands = "";
		for($b = 0; $b < $maxBrands; $b++) {
			$brands .= $profile['brands'][$b]. ", ";
		}
		// To avoid a comma at the end, the last brand is added after the loop.
		$brands .= $profile['brands'][$b];
		echo boldShow("Favorite brands", $brands);
		
		// The like-status should be displayed.
		if($this->authentication->userLoggedIn()) {
			if($profile['likestatus'][0]) 
			{
				if($profile['likestatus'][1])
				{
				    echo "You both like each other". br();
				}
				else
				{
				    echo "You liked this user, but have yet to receiev a like back...". br();
				}
			}
			else if($profile['likestatus'][1])  
			{
			    echo "The other user liked you, like back?". br();
			}
			else
			{ 
			    echo "this user has not liked you yet, but you can be the first...". br();
			}
			
			if($profileType == 'big' && !$profile['likestatus'][0]) {
				
				echo "<h3 id='likebutton'>". htmlspecialchars("<< Click here to like user >>"). "</h3>";
			}
			else {
				echo getLikePicture($profile['likestatus']);
			}
		}
		
		// If the profilebox is big, the div is closed
		if($profileType == "big") {
			echo "</div>";
		}
		else {	// and if it is small, it's link is closed.
			echo "</a>";
		}
	}
?>
</div>

<script type="text/javascript">
// Add mouse listener to the likebutton. 

$('#likebutton').click(function()
{
	var ok = confirm("<?php echo 'Do you want to like '. $profile['username']. '?'; ?>");

	if(ok)
	{
		// Send like request. 
		$.post('<?php echo base_url(). '/index.php/viewprofile/like'; ?>',
			   {otherId: <?php echo $profile['userId'] ?>},
			   function()
			   {
					// Refresh page when done. 
					window.location.reload();
			   });
	}
});
</script><file_sep><?php

if($error !== null)
{
    echo '<p class="error">' . form_prep($error) . '</p>';
}

$n = '<br/>';

echo   form_open('adminpanel', array('id' => 'adminform'))

  .$n. form_label('Similarity measure:', 'similarityMeasure')
  .$n. form_dropdown('similarityMeasure', array(
                                           "Dice's",
                                           "Jaccard's",
                                           "Cosine",
                                           "Overlap"
                                          ), $similarityMeasure)

  .$n. form_label('X-factor (0-1):', 'xFactor')
  .$n. form_input('xFactor', $xFactor, array('type' => 'range', 'min'=> '0', 'max' => '1'))

  .$n. form_label('Alpha factor (0-1):', 'alpha')
  .$n. form_input('alpha', $alpha, array('type' => 'range', 'min'=> '0', 'max' => '1'))

  .$n. form_submit('submit', 'Apply')
  .$n. form_close()
  ;
<file_sep><?php 
$base = base_url() . 'index.php';
$ci =& get_instance();
$admin = $ci->authentication->userIsAdmin();
?>


	<nav>
    	<ul>
        	<li><a href="<?php echo "$base/home"?>">Home</a></li>
        	<li><a href="<?php echo "$base/search"?>">Search</a></li>
	        <?php 
	        if($this->authentication->userLoggedIn()) {
	        	echo "<li><a href=\"$base/search/matching\">Match!</a></li>";
	        	echo "<li><a href=\"$base/profileUpdate\">Edit Profile</a></li>";
	        	echo "<li><a href=\"$base/search/likes\">Likes</a></li>";
	        }
	        if($admin) {
	        	echo "<li><a href=\"$base/adminpanel\">Configuration</a></li>";
	        }
	        ?>
    	</ul>
	</nav>
	<section id='main'><file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Test extends CI_Controller 
{

    public function index()
    {
        $this->load->helper('url');
        $this->load->helper('security');
        $this->load->model('User', 'user');
        $this->load->library('Personality', 'personality');
        $this->load->model('Matching', 'matching');
        $this->load->model('Brand', 'brand');
        
        // User generator.
        $allBrands = $this->brand->getBrands();
        for($i = 1; $i < 50; ++$i)
        {
            $brands = array_rand(array_flip($allBrands), rand(2, 50));
            $data = array(
                    'username' => "ExtraTestuser$i",
                    'email' => "<EMAIL>",
                    'firstName' => '<NAME>',
                    'lastName' => 'The ' . $i . ' th',
                    'gender' => $i % 2 == 0,
                    'birthdate' => '198' . $i % 10 . '-01-01',
                    'description' => 'Ik ben een testgebruiker om dit geheel een beetje op te vullen. Alleen dan wat beter gebalanceerd.',
                    'minAgePref' => 18,
                    'maxAgePref' => 100,
                    'genderPref' => $i % 3 == 0 ? null : $i % 3 == 1,
                    'personalityI' => rand(0, 100) / 100,
                    'personalityN' => rand(0, 100) / 100,
                    'personalityT' => rand(0, 100) / 100,
                    'personalityJ' => rand(0, 100) / 100,
                    'preferenceI' => rand(0, 100) / 100,
                    'preferenceN' => rand(0, 100) / 100,
                    'preferenceT' => rand(0, 100) / 100,
                    'preferenceJ' => rand(0, 100) / 100    
            );
            $this->user->createUser($data, 'test12345', $brands);
            /*print_r($brands);
            echo "<br/>";
            echo "<br/>";
            echo "<br/>";*/
        }
                
        //$id = $this->user->createUser($data, 'hoi', $brands);
        /*$this->user->deleteSelf();
        $id = $this->user->createUser($data, 'hoi', $brands);
        $user = $this->user->load(1);
        print_r($user['genderPref'] === '0');*/
        
        //print_r($this->user->load(1));
        //print_r($this->matching->matchingList(1));
        
        //$this->user->like(2);
        //print_r($this->user->getLikeStatus(2));
        
        //$this->parser->parse('searchbrowser', array('bla' => array('Blah!', 'Blablah!')));
        
        /*if(count($_FILES) > 0)
        {
            $this->load->library('picture');
            print_r($this->picture->uploadAndProcess());
        }
        else
        {
            echo form_open_multipart() . form_upload('picture') . form_submit('submit', 'go') . form_close();
        }*/
    }
}

<file_sep>
<div id="searchbrowsebuttons">
    <p id="previous"> &lt&lt Previous page </p>
    <p id="next"> Next page &gt&gt </p> 
</div>

<script type="text/javascript">

$(document).ready(function()
{
    // Put the received user id's in a global using the template parser. 
    userIds = [0 {ids} , {id} {/ids} ];
    // Remove initial zero. 
    userIds.splice(0,1);
    
    // The current page of results. 
    currentPage = 0;
    // The total number of pages, equal to the number of id's divided by 6 (and rounded upwards). 
    nofPages = Math.ceil(userIds.length  / 6);
    
    // Hide the previous button.
    $('#previous').hide();
    
    // Hide the nest button if there is only one page. 
    if(nofPages <= 1)
    {
        $('#next').hide();
    }
    
    // Function for getting a certain page through AJAX.
    function getPage(page)
    {
        // Determine id's on this page. 
        var ids = '';
        for(var i = page * 6; i < page * 6 + 6 && i < userIds.length; ++i)
        {
            ids += '/' + userIds[i];
        }
    
        // Do an AJAX request to update the displayed search results. 
        $('#profileArea').load('<?php echo base_url() . '/index.php/search/displayProfiles' ?>' + ids);
    }
    
    // Add mouse listeners to browser buttons. 
    $('#previous').click(function() 
    {
        if(currentPage > 0)
        {
            // Load previous page. 
            getPage(--currentPage);
    
            // Show next button again, if neccessary. 
            if($('#next').is(':hidden'))
            {
                $('#next').show();
            }
    
            // Hide previous button if back at first page. 
            if(currentPage == 0)
            {
                $('#previous').hide();
            }
        }
    });
    $('#next').click(function() 
    {
        if(currentPage < nofPages - 1)
        {
            // Load next page. 
            getPage(++currentPage);
    
            // Show prev button, if it was hidden before. 
            if($('#previous').is(':hidden'))
            {
                $('#previous').show();
            }
    
            // Hide next button if at last page. 
            if(currentPage == nofPages - 1)
            {
                $('#next').hide();
            }
        }
    });
});

</script> 
<file_sep><?php 
	$base = base_url() . 'index.php';
?>
	</section>
	<footer>
        <ul>
            <li><a href="<?php echo "$base/about"?>">About</a></li>
            <li><a href="<?php echo "$base/howto"?>">Help</a></li>
            <li><a href="<?php echo "$base/termsofuse"?>">Terms of use</a></li>
            <li><a href="<?php echo "$base/privacypolicy"?>">Privicy (nihil.)</a></li>
        </ul>
    </footer>
    </div>
</body>
</html> <file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class ProfileUpdate extends CI_Controller {

// After succesfully validating a filled in form, use the POST data to add a new user to the
	// database.
    private function _updateUser()
	{
	    // Load libraries and models.
	    $this->load->library('picture');
	    $this->load->model('user');
	    
	    // Fetch input.
	    $input = $this->input->post();
	    
	    // This will be contain the parsed data as added to the user model.
	    $data = array();
	    
	    // Values that can be directly put to the data array (after a rename of their keys).
	    $rename = array(
	                'username' => 'username',
	                'firstName' => 'firstName',
	                'lastName' => 'lastName',
	                'email' => 'email',
	                'gender' => 'gender',
	                'description' => 'description',
	                'ageprefmin' => 'minAgePref',
	                'ageprefmax' => 'maxAgePref',
	                'birthdate' => 'birthdate'
	              );
	    foreach($rename as $key => $newkey)
	    {
	        if(isset($input[$key]))	
	    		$data[$newkey] = $input[$key];
	    }
	    
	    // Parse gender preference.
	    $data['genderPref'] = $input['genderPref'] == 2 ? null : (int) $input['genderPref'];
	    
	    // Parse brand preferences.
	    if(isset($input['brandpref']))
	    	$brands = array_keys(array_filter($input['brandpref']));
	    
		$profile = $this->user->load($this->authentication->currentUserId());
	    
	    foreach($profile as $key => $val) {
	    	if(isset($data[$key]))
	    		$profile[$key] = $data[$key]; 
	    }
	    
	    // Process uploaded image. 'picture' will be set to null if none are specified.
	    $data['picture'] = $this->picture->uploadAndProcess();
	    if(!$data['picture'] == null)
	    {
	        $profile['picture'] = $data['picture']; 
	    }
	    
	    // Actually create the user.
	    $this->user->updateSelf($profile);
	}
	
	public function index()
	{
        $this->load->model('user','',true);
        $this->load->library(array('personality', 'form_validation', 'upload'));
        $this->load->helper(array('html', 'form'));
		
		$this->load->view('header',array("pagename" => "Edit Profile"));
        $this->load->view('loginbox');
		$this->load->view('nav');
		
		$config = array(
				array(
        			'field' => 'firstName',
        			'label' => 'Firstname',
        			'rules' => 'required'
				),
				array(
        			'field' => 'lastName',
        			'label' => 'Lastname',
        			'rules' => 'required'
				),
				array(
        			'field' => 'email',
        			'label' => 'Email',
        			'rules' => 'required|valid_email'
				),
				array(
        			'field' => 'gender',
        			'label' => 'Gender',
        			'rules' => 'required'
				),
				array(
        			'field' => 'birthdate',
        			'label' => 'Birthdate',
        			'rules' => 'required|alpha-dash|callback_validateDate'
				),
				array(
        			'field' => 'description',
        			'label' => 'About you',
        			'rules' => ''
				),
				array(
        			'field' => 'genderPref',
        			'label' => 'Gender preference',
        			'rules' => 'required'
				),
				array(
        			'field' => 'ageprefmin',
        			'label' => 'Minimum age',
        			'rules' => 'required|numeric|greater_than[17]|callback_validAgePref[ageprefmax]'
				),
				array(
        			'field' => 'ageprefmax',
        			'label' => 'Maximum age',
        			'rules' => 'required|numeric|less_than[123]'
				),
				array(
                    'field' => 'picture',
                    'label' => 'Upload picture',
                    'rules' => ''				
				)
        );
		
        // The list of brands is loaded from the database
        $this->load->model('brand');
        $brands = $this->brand->getBrands();
        
		foreach($brands as $brand)
	    {
			$config[] = array(
	        				'field' => $brand,
	        				'label' => $brand,
	        				'rules' => '');
	    }
	    
        // And all the rules are added to the form_validation
        $this->form_validation->set_rules($config);
        // All errors are placed in a div with the error class
        $this->form_validation->set_error_delimiters('<div class="error">', '</div>');
        
        // The profile is loaded
        $profile = $this->user->load($this->authentication->currentUserId());
        
        // Check if there is a post result. If there is, the brandpref list is handed
        // to the brandCheckBoxes function for re-populating.
        if(array_key_exists('brandpref',$_POST)) {
        	$brandpref = $_POST['brandpref'];
        }
        else {
        	// If there is a profile, its brandspreferences are used as the re-populate list.
        	$brandpref = $profile['brands'];
        }
        
        $data = array('profile'			 => $profile,
        			  'brands'           => $brands,
                      'brandPreferences' => $brandpref);
        
        // Display the form while it is invalid.
        if ($this->form_validation->run() === false) 
        {
            $this->load->view('content/registerView', $data);
		}
		else 
		{
    	    // The form input is validated, the user is registeredand the succesmessage is displayed.
    		$this->_updateUser();
    		$data['succesmessage'] =  "You have succesfully updated your profile";
    		$this->load->view('content/registerView', $data);
		}
        
        $this->load->view('footer');
	}
	
	/**
	 * 
	 * Our own date-validation rule.
	 * @param string $date	The date that will be checked
	 * @return boolean		False if failed
	 */
	public function validateDate($date)
	{
		$dummy = array();
	    $validDate = !!preg_match_all('/[0-9]{4}-[0-9]{2}-[0-9]{2}/', $date, $dummy);
	    $year; $month; $day;
	    sscanf($date, '%D-%D-%D', $year, $month, $day);
	    $thisYear = date('o');
	    $validDate &= $year > $thisYear - 122 && $year <= $thisYear - 18
	               && $month >=  1 && $month <= 12
	               && $day >= 1 && $day <= 31;
	    
	    if(!$validDate) {
	    	if(is_numeric($year) && $year < $thisYear - 122)
	    		$this->form_validation->set_message('validateDate', 
							'If you are older than 122, please contact the Guiness Book of World Records.');
	    	else 
	    		$this->form_validation->set_message('validateDate', 
							'Invalid date');
			return false;
	    }
	    else {
	    	return true;
	    }
	}
	
	/**
	 * 
	 * Checks if ageprefmin is smaller or equal to ageprefmax.
	 * @param int $ageprefmin		The minimum age preference
	 * @param string $ageprefmax	The name for the maximum age preference in the post
	 * @return boolean 				false if fails
	 */
	public function validAgePref($ageprefmin, $ageprefmax)
	{
		if($ageprefmin > $this->input->post($ageprefmax)) {
			$this->form_validation->set_message('validAgePref', 
							'The %s field can not be greater than the Maximum age');
			return false;
		}
		else {
			return true;
		}
	}
	
	public function check_password($password)
	{
		$this->load->model('user','',true);
		$profile = $this->user->load($this->authentication->currentUserID(), array('email'));
		
		if($this->user->lookup($profile['email'], $password) == null) {
			$this->form_validation->set_message('check_password', 
							'Incorrect password');
			return false;
		}
		else return true;
	}
}	
?><file_sep>
<?php
	echo heading("Welcome $firstName $lastName!",3). br(2). 
			"You can log in with your email and password.<br />";
?><file_sep><section id='registerblock'>
<?php
	
	if(isset($succesmessage))
		echo heading($succesmessage, 2). br(2);
	
	/*
	 * First some variables needed for creating and populating the fields are defined
	 */
	
	// If no profile is submitted, profileEdit is set to false and the profile to null.
	$profileEdit = isset($profile);
	if(!$profileEdit) $profile = null;
	
    // Some inputs are selects from a list of possibilities.
    // The list of gender options is prepared
    $genders = array( '0' => 'Male',
        			  '1' => 'Female');
    // The list of possible gender preferences
    $genderprefs = array( '0' => 'Male',
        			  	  '1' => 'Female',
        				  '2' => 'Either');
	
    /*
     * Then some functions for building the html for the form fields
     */
    
	/**
	 * The brandprefs are returned in an array. CodeIgniter can not handle an array in its value
	 * set functions, so we made our own. :-)
	 * 
	 * brandCheckBoxes turns an array of brands into html which displays a list of checkboxes.
	 * If the form was posted, the checkboxes are re-populated.
	 * @param array $brands			List of brands
	 * @param array $brandprefs		List of checked brands
	 * @return string				html for a list of checkboxes
	 */
	function brandCheckBoxes(array $brands, array $brandprefs = null)
	{
		$html = form_fieldset("Brand-preferences", array('class' => 'brands')). 
				form_error('brandpref[]') .'<ul class="brandslist">';
		foreach($brands as $brand) {
			$html .= '<li>'. form_checkbox('brandpref[]',$brand, 
						$brandprefs != null && in_array($brand,$brandprefs)). $brand. "</li>";
		}
		return $html. "</ul></fieldset>";
	}
	
	function buildField($heading, $name, $size = 50, $profileEdit = false, 
							array $profile = null, $password = false)
	{
		$output = heading($heading, 4). form_error($name);
		
		// If the field is a passwordfiel, it should not be re-populated
		if($password) $value = '';
		else if($profileEdit) $value = set_value($name, $profile[$name]);
		else $value = set_value($name);
		
		// The input parameters are set. 
		// If the field is a password, the type is set to password, else text.
		$data = array('name'  => $name,
					  'value' => $value,
					  'size'  => $size,
					  'type'  => $password ? 'password' : 'text'
		              );
		$output .= form_input($data). br(2);
		echo $output;
	}
	
	function buildDropdown($heading, $name, $filling, $profileEdit = false, array $profile = null)
	{
		$output = heading($heading, 4). form_error($name);
		$output .= form_dropdown($name, $filling, set_value($name, $profile[$name])). br(2);
		echo $output;
	}
	
	
	/*
	 * Start of the form
	 */
	
	if($profileEdit)
		echo form_open_multipart('profileUpdate');
	else echo form_open_multipart('register');
	
	// If the profile is being edited, display a welcome message with the username
	if($profileEdit) echo heading('Hi '. $profile['username']. '!', 2);
	// Else, offer a field to register a username
	else buildField('Username', 'username');
	
	buildField('First name', 'firstName', 50, $profileEdit, $profile);
	buildField('Last name', 'lastName', 50, $profileEdit, $profile);
	
	// If the profile is being edited, one field for the old password and two for the new one are displayed 
	if($profileEdit) {
		/*buildField('Old password', '<PASSWORD>', 50, false, null, true);
		buildField('New password', '<PASSWORD>', 50, false, null, true);
		buildField('Repeat new password', '<PASSWORD>conf', 50, false, null, true);*/
	}
	else {  // Else, only two password fields are shown to register a password
		buildField('Password', '<PASSWORD>', 50, false, null, '<PASSWORD>');
		buildField('Repeat password', '<PASSWORD>', 50, false, null, 'password');
	}
	buildField('Email', 'email', 50, $profileEdit, $profile);
	buildDropdown('Gender', 'gender', $genders, $profileEdit, $profile);
	buildField('Birthdate (yyyy-mm-dd)', 'birthdate', 50, $profileEdit, $profile);  
?>

	<h4>About me</h4>
	<?php echo form_error('description'); ?>
	<textarea name="description" rows="5" cols="37"><?php echo set_value('description', $profile['description']); ?></textarea>
	<br /><br />
	
	<?php buildDropdown('Gender preference', 'genderPref', $genderprefs, $profileEdit, $profile); ?>
	
	<h4>Age preference</h4>
	<?php echo form_error('ageprefmin');
		  echo form_error('ageprefmax'); 
			$min = $profileEdit ? $profile['minAgePref'] : 18;
			$max = $profileEdit ? $profile['maxAgePref'] : 122; ?>
		Minimum: 
		<input type="text" name="ageprefmin" value="<?php echo set_value('ageprefmin', $min); ?>"/>
		Maximum:
		<input type="text" name="ageprefmax" value="<?php echo set_value('ageprefmax', $max); ?>"/>
		<br /><br />
		
	<h4>Upload picture (JPEG format required, should be smaller than 1MB) </h4>
	<?php echo form_error('picture'); 
	      echo form_upload('picture'); ?>
          <br /><br />
	
	<?php echo brandCheckBoxes($brands, $brandPreferences). br(); ?>
	
	<?php
	
	if(!$profileEdit) {
	
	$questions = array(1  => array('A' => "Ik geef de voorkeur aan grote groepen mensen, met een grote diversiteit.",
								   'B' => "Ik geef de voorkeur aan intieme bijeenkomsten met uitsluitend goede vrienden."),
					   2  => array('A' => "Ik doe eerst, en dan denk ik.",
								   'B' => "Ik denk eerst, en dan doe ik."),
					   3  => array('A' => "Ik ben makkelijk afgeleid, met minder aandacht voor een enkele specifieke taak.",
								   'B' => "Ik kan me goed focussen, met minder aandacht voor het grote geheel."),
					   4  => array('A' => "Ik ben een makkelijke prater en ga graag uit.",
								   'B' => "Ik ben een goede luisteraar en meer een privé-persoon."),
					   5  => array('A' => "Als gastheer/-vrouw ben ik altijd het centrum van de belangstelling.",
								   'B' => "Als gastheer/-vrouw ben altijd achter de schermen bezig om te zorgen dat alles soepeltjes verloopt."),
					   6  => array('A' => "Ik geef de voorkeur aan concepten en het leren op basis van associaties.",
								   'B' => "Ik geef de voorkeur aan observaties en het leren op basis van feiten."),
					   7  => array('A' => "Ik heb een groot inbeeldingsvermogen en heb een globaal overzicht van een project.",
								   'B' => "Ik ben pragmatisch ingesteld en heb een gedetailleerd beeld van elke stap."),
					   8  => array('A' => "Ik kijk naar de toekomst.",
								   'B' => "Ik houd mijn blik op het heden gericht."),
					   9  => array('A' => "Ik houd van de veranderlijkheid in relaties en taken.",
								   'B' => "Ik houd van de voorspelbaarheid in relaties en taken."),
					   10 => array('A' => "Ik overdenk een beslissing helemaal.",
								   'B' => "Ik beslis met mijn gevoel."),
					   11 => array('A' => "Ik werk het beste met een lijst plussen en minnen.",
								   'B' => "Ik beslis op basis van de gevolgen voor mensen."),
					   12 => array('A' => "Ik ben van nature kritisch.",
								   'B' => "Ik maak het mensen graag naar de zin."),
					   13 => array('A' => "Ik ben eerder eerlijk dan tactisch.",
								   'B' => "Ik ben eerder tactisch dan eerlijk."),
					   14 => array('A' => "Ik houd van vertrouwde situaties.",
								   'B' => "Ik houd van flexibele situaties."),
					   15 => array('A' => "Ik plan alles, met een to-do lijstje in mijn hand.",
								   'B' => "Ik wacht tot er meerdere ideeën opborrelen en kies dan on-the-fly wat te doen.",),
					   16 => array('A' => "Ik houd van het afronden van projecten.",
								   'B' => "Ik houd van het opstarten van projecten.",),
					   17 => array('A' => "Ik ervaar stress door een gebrek aan planning en abrupte wijzigingen.",
								   'B' => "Ik ervaar gedetailleerde plannen als benauwend en kijk uit naar veranderingen."),
					   18 => array('A' => "Het is waarschijnlijker dat ik een doel bereik.",
								   'B' => "Het is waarschijnlijker dat ik een kans zie."),
					   19 => array('A' => "\"All play and no work maakt dat het project niet afkomt.\"",
								   'B' => "\"All work and no play maakt je maar een saaie pief.\""),
				 );
	
	// The questions order is randomized
	shuffle($questions);
	
	// The C-answere, which is always the same, is added to the questions. Of course, after the shuffle.
	$questions['C'] = "Ik zit er eigenlijk tussenin.";
				 
	// The questions array is turned into valid html with re-population
	$questionsHtml = array();
	for($q = 1; $q < count($questions); $q++) {
		if(form_error("question$q") != '') $line =  form_error("question$q");
		else $line = br();
		echo heading("Question $q:",4). $line. 
				form_radio("question$q",'A',set_value("question$q") == 'A'). utf8_encode($questions[$q-1]['A']). br().
				form_radio("question$q",'B',set_value("question$q") == 'B'). utf8_encode($questions[$q-1]['B']). br().
				form_radio("question$q",'C',set_value("question$q") == 'C'). utf8_encode($questions['C']). br(2);
	
	}
	}
	?>
	
	<br />
	<div><input type="submit" value="Submit" /></div>

	</form>
</section><file_sep><?php

class LoginSystem extends CI_Controller
{
	
	function __construct() {
        parent::__construct();
    }
    
    public function login()
    {        
        $email = $this->input->post('email');
        $password = $this->input->post('password');

        if($email === false || $password === false)
        {
            // Show regular page if nothing in POST.
            $this->index();
            return;
        }
        
        $id = $this->authentication->login($email, $password);
        
        $error = null;
        if($id === null)
        {
            $error = 'Login error: this e-mail/password combination does not exist.';
        }
        
        $this->index($error);
    }
    
    public function logout()
    {
        // Log out the current user and show homepage.
        $this->authentication->logout();
        
        $this->index();
    }
    
    public function delete()
    {
        $sure = $this->input->post('sure');
        
        if($sure == 'yes')
        {
            $this->model->load('User', 'user');
            
            $this->user->deleteSelf();
            $this->authentication->logout();
        }
    }
}
?><file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Configuration extends CI_Model
{
    /**
     * Constants representing similarity measure coefficients.
     */
    const SMC_DICE    = 0,
          SMC_JACCARD = 1,
          SMC_COSINE  = 2,
          SMC_OVERLAP = 3;
    
    /**
     * Load the current settings.
     * 
     * @return array(string => int/float) The settings and their current values.
     */
    public function load()
    {
        $settings = array(
                      'similarityMeasure',
                      'xFactor',
                      'alpha'
                    );
        
        $result = $this->db->select($settings)
                       ->from('Configuration')
                       ->get()->row_array();
        
        return $result;
    }
    
    /**
     * Save changed settings.
     *
     * @param array(string => int/float) $data The changed settings. Should already be validated!
     */
    public function save($data)
    {       
        // Make sure an admin is doing this.
        $this->authentication->assertAdministrator();
        
        $this->db->update('Configuration', $data);
    }
}<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

include_once('profilebrowser.php');
class Home extends ProfileBrowser
{

    public function index($msg = null)
	{
        $this->load->model('user','',true);
		
		$this->load->view('header',array('pagename' => 'Home'));
        $this->load->view('loginbox');
		$this->load->view('nav');
		
		
		// Show message if specified.
		if($msg !== null)
		{
		    $this->parser->parse('msgbox', array('message' => $msg));
		}
        
        $this->load->view('content/showcase');
        // Get a list of 600 userid's in random order and put them in the profile browser
    	$this->userBrowser($this->user->getRandomUsers(600));
        
        $this->load->view('footer');
	}
	
	public function updateUser()
	{
		$this->load->library('form_validation');
		
		if ($this->form_validation->run() == false) {
			$this->load->view('myform');
		}
		else {
			$this->load->view('formsuccess');
		}
		
		
		
		$this->load->helper('html');
		$data = array('user' => array('text' => 'Smoked yah!'));
		$this->load->view('content/testView', $data);
	}
    
    
    public function login()
    {        
        $email = $this->input->post('email');
        $password = $this->input->post('password');

        if($email === false || $password === false)
        {
            // Show regular page if nothing in POST.
            $this->index();
            return;
        }
        
        $id = $this->authentication->login($email, $password);
        
        $error = null;
        if($id === null)
        {
            $error = 'Login error: this e-mail/password combination does not exist.';
        }
        
        $this->index($error);
    }
    
    public function logout()
    {
        // Log out the current user and show homepage.
        $this->authentication->logout();
        
        $this->index();
    }
    
    public function delete()
    {
        $sure = $this->input->post('sure');
        
        if($sure == 'yes')
        {
            $this->load->model('User', 'user');
            
            $this->user->deleteSelf();
            $this->authentication->logout();
        }
    }
   
}    
<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Matching extends CI_Model
{
    // Determine the length on the union of two arrays in O(n). $arrayA should've been flipped.
    private function _unionLength($arrayA, $arrayB)
    {
        $result = count($arrayA);
        foreach($arrayB as $val)
        {
            if(!isset($arrayA[$val['brandName']]))
            {
                ++$result;
            }
        }
        
        return $result;
    }
    
    // Determine the length on the intersection of two arrays in O(n). $arrayA should've been 
    // flipped.
    private function _intersectLength($arrayA, $arrayB)
    {
        $result = 0;
        foreach($arrayB as $val)
        {
            if(isset($arrayA[$val['brandName']]))
            {
                ++$result;
            }
        }
        
        return $result;
    }
    
    private function _distance($similarityMeasure, $xFactor, $userA, $userB)
    {
        // First determine distance between personality types and preferences.
        $pd1 = ($userA->personalityI - $userB->preferenceI)
             - ($userA->personalityN - $userB->preferenceN)
             - ($userA->personalityT - $userB->preferenceT)
             - ($userA->personalityJ - $userB->preferenceJ);
        $pd2 = ($userB->personalityI - $userA->preferenceI)
             - ($userB->personalityN - $userA->preferenceN)
             - ($userB->personalityT - $userA->preferenceT)
             - ($userB->personalityJ - $userA->preferenceJ);
        
        // Take maximum of absolute distances and divide by four.
        $personalityDistance = max(abs($pd1), abs($pd2)) / 4;
        
        // Make the brand names of userA keys instead of values, effectively turning it into a hash
        // table with O(1) lookup time. 
        $brandKeys = array_flip($userA->brands);
        
        // Calculate distance between brand preferences by using the currently condifured 
        // similarity measure.
        $alen = count($userA->brands);
        $blen = count($userB->brands);
        $i = $this->_intersectLength($brandKeys, $userB->brands);
        $similarity;
        switch($similarityMeasure)
        {
            case 0: // Dice
                $similarity = 2 * $i / ($alen + $blen);
                break;
            case 1: // Jaccard
                $similarity = $i / $this->_unionLength($brandKeys, $userB->brands); 
                break;
            case 2: // cosine
                $similarity = $i / (sqrt($alen) * sqrt($blen));
                break;
            case 3: // overlap
                $similarity = $i / min($alen, $blen);
                break;
        }
        
        // Brand distance is the opposite of the brand's similarity.
        $brandDistance = 1 - $similarity;
        
        // Now return the total distance based on the current x-factor.
        return $xFactor * $personalityDistance 
             + (1 - $xFactor) * $brandDistance;
    }
    
    /**
     * Build a list of all users matching to a certain one, sorted on distance.
     * 
     * @param $userId The id of the user to match on.
     * 
     * @return array(int) The userId's of the matching users.
     */
    public function matchingList($userId)
    {
        // Query expression to determine the age of a user.
        $age = "(strftime('%Y', 'now') - strftime('%Y', birthdate)
              - (strftime('%j', 'now') < strftime('%j', birthdate)))";
        
        // Select neccessary data from user to match with.
        $user = $this->db->select(array('gender', 'genderPref', 'birthdate', 'minAgePref', 
                                        "$age AS age", 'maxAgePref',
                                        'personalityI', 'personalityN', 'personalityT',
                                        'personalityJ', 'preferenceI', 'preferenceN', 
                                        'preferenceT', 'preferenceJ'))
                         ->from('Users')
                         ->where('userId', $userId)
                         ->get()->row();
        
        if(!$user)
        {
            throw new Exception('User not found.');
        }
        
        // Start building a query on the users table.
        // TODO: Specify which columns are necessary.
        $query = $this->db->select()->from('Users');
        
        
        // First disallow matching the user with itself. Because recommending people to date 
        // themselves would be pretty weird. 
        $query->where('userId <>', $userId);
        
        // Constrain on gender and mutual preference.
        if($user->genderPref !== null)
        {
            $query->where('gender', $user->genderPref);
        }
        
        // Note: We only append a zero or one to the query, therefore this is not unsafe.
        $query->where('(genderPref IS NULL OR genderPref = ' . ($user->gender ? '1' : '0') . ')');
        
        
        // Now constrain on mutual age preference.
        $query->where("$age >=", (int) $user->minAgePref);
        $query->where("$age <=", (int) $user->maxAgePref);
        $query->where('minAgePref <=', (int) $user->age);
        $query->where('maxAgePref >=', (int) $user->age);
        
        
        // Do query.
        $matches = $query->get()->result();
        
        // Determine brand preferences of current user.
        $brands = $this->db->select('ub.brandName')
                           ->from('UserBrands ub')
                           ->join('Brands b', 'ub.brandName = b.brandName', 'left')
                           ->where('userId', $userId)
                           ->get()->result();
        
        $user->brands = array();
        foreach($brands as $brand)
        {
            $user->brands[] = $brand->brandName;
        }
        
        // Determine current similarity measure and x-factor.
        $configs = $this->db->select(array('similarityMeasure', 'xFactor'))
                            ->from('Configuration')
                            ->get()->row();;
        
        // Now compute personality distances between this users and possible matches.
        $distances = array();
        foreach($matches as &$match)
        {
            // First determine brand preferences for this user.
            $match->brands = $this->db->select('ub.brandName')
                                  ->from('UserBrands ub')
                                  ->join('Brands b', 'ub.brandName = b.brandName', 'left')
                                  ->where('userId', $match->userId)
                                  ->get()->result_array();
            
            // Now calculate the distance to this user.
            $distances[] = $this->_distance($configs->similarityMeasure, $configs->xFactor, $user, $match);
        }
        
        // Only return the id's.
        foreach($matches as &$match)
        {
            $match = $match->userId;
        }
        
        // Sort the matches on these distances.
        array_multisort($distances, SORT_NUMERIC, $matches);
        
        // Return the ordered matches.
        return $matches;
    }
}<file_sep>Webtechnologie practicum 3 - Datingsite

===============================================================================
=                        Door:                                                =
=                          <NAME>   -  3470644                           = 
=                          <NAME>  -  3470784                           =
=                                                                             =
=                        URL website:                                         =
=                 www.students.science.uu.nl/~3470784/webtech3                =
===============================================================================
_______________________________________________________________________________

Geteste browsers:
 - Mozilla Firefox
 - Google Chrome
 
_______________________________________________________________________________

Bestanden:
 - Een databasemodel is te vinden in dbmodel.png.
 - De databasedefinitie staat in database.sql.
 - brands.txt bevat een lijst van gebruikte merknamen.
 - De daadwerkelijke database (met de creatieve naam database.db) staat in www/database.
 - In de www-map staat alles wat op de server dient te staan. www/system bevat de CodeIgniter
    -libraries, terwijl in www/application de daadwerkelijke applicatiecode staat, op de manier die
    CodeIgniter vereist.
 - www/img bevat afbeeldingen, www/js bevat JQuery, www/css de stylesheets, www/uploads wordt als
    tijdelijke opslagruimte voor geüploade foto's gebruikt, voordat deze worden verwerkt.
 - Alle submappen van www, behalve css, img en js, worden afgeschermt door een .htaccess-bestand 
    die niemand toestaat. Hierdoor is het niet mogelijk bijvoorbeeld via de URL eens even de inhoud
    van de database te bekijken. www/.htaccess herschrijft verder de 'http' in de URL om naar 
    'https', om HTTPS af te dwingen voor de gehele website.

 
_______________________________________________________________________________

Toelichting:
 
 - Elke pagina begint met drie 'header' views, namelijk header (begintags, titel e.d.), nav (de 
    navigatiebalk), en loginbox (waarmee anonieme gebruikers kunnen inloggen of naar het 
    registratieformulier gaan en geregistreerde gebruikers kunnen uitloggen of hun account deleten).
    Elke pagina wordt ook afgesloten met een 'footer'.
    
 - De home-controller is de belangrijkste controller, die zowel het inloggen regelt als de 
    hoofdpagina weergeeft.
    
 - Voor authenticatie is de authentication-library, die weer gebruik maakt van CodeIgniter's sessie-
    mechanisme en de database. In de sessiedata van een ingelogde gebruiker wordt opgeslagen wat 
    de 'userId' van deze gebruiker is, aan de hand waarvan gegevens uit de database kunnen worden
    gehaald. CodeIgniter zorgt ervoor dat sessie-id's in de cookie van de gebruiker gematcht worden
    met de database.
    
 - Het model 'user' bevat verschillende methoden die betrekking hebben op de gebruikerstabel in de
    database, zoals het laden van gebruikers, het creeëren van net geregistreerde gebruikers en het
    toevoegen van likes.
    
 - Het model 'matching' voert matching uit met een bepaalde gebruiker. Het filteren van gebruikers 
    op basis van leeftijd(svoorkeur) en geslacht(svoorkeur) gebeurt via de query, terwijl het
    sorteren op basis van matching distance gebeurt op PHP-niveau.
    
 - De profile-view representeerd een gebruikersprofiel, dit kan zowel een kort overzicht voor 
    zoekresultaten zijn als de volledige pagina die ook nog eens bewerkbaar kan zijn voor de 
    eigenaar van het profiel.
    
 - De zoekfuncties in de search-controller (zowel zoeken op eigenschappen als matchende gebruikers
    in volgorde opvragen) leveren een lijst van user identifiers op (net als de generator van zes 
    willekeurige gebruikers uit het usermodel), deze kunnen vervolgens worden gebruikt door de 
    'profilebrowser'-controller. Deze weergeeft de eerste zes profielen en plakt alle (of tenminste 
    de eerste 600, verder zal de gebruiker niet doorklikken) opgeleverde userId's in een Javascript 
    (de searchbrowser-view). Dit script vraagt vervolgens, na het klikken op de next- of previous-
    knop, via AJAX de profielen te tonen van de volgende zes id's. Deze identifiers worden 
    simpelweg via de URL doorgegeven.
    
_______________________________________________________________________________

Testadminstrator:
 - Gebruikersnaam: admin
 - Wachtwoord: puddingtaart
 
Testgebruikers:
 - E-mail: <EMAIL>
 - Wachtwoord: swordfish
 
 - E-mail: <EMAIL>
 - Wachtwoord: 0xE29883
<file_sep><?php 
$base = base_url() . 'index.php/search/likes';
?>

<div id="likebox">
<h1>What do you want to display?</h1>
	<a href="<?php echo "$base/mutual" ?>">People you like, that like you</a><br/>
	<a href="<?php echo "$base/liking" ?>">People liking you</a><br/>
	<a href="<?php echo "$base/liked" ?>">People you like</a><br/>
</div><file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 

class Personality
{
    
    /**
     * This function takes an associative array containing personality values (e.g. the result of
     * User::load); these values have keys 'personalityI', 'personalityN', 'personalityT' or
     * 'personalityJ' and a floating point number between 0 and 1 as value. Each of these present
     * in the input array will either be returned directly or, if their value is lower than 0.5, the
     * opposite letter of the same dicotomy will be given instead. 
     * 
     * @param array(string => mixed) $arr        The input associative array. Will not be modified.
     * @param bool                   $preference If true, this will work on preferences rather than 
     *                                           own personalities.
     * 
     * @return array(string => float) The resulting dominant personality values, e.g. 
     *                                array(I => 0.8, N => 0.7,  T => 0.51, J => 0.65).      
     */
    public function dominantPersonalityComponents($arr, $preference = false)
    {
        // Determine whether to look at personality or preference.
        $subject;
        if($preference)
        {
            $subject = 'preference';
        }
        else
        {
            $subject = 'personality';
        }
        $slen = strlen($subject);
        
        // Mapping of components to their opposites. 
        $opposites = array('I' => 'E', 'N' => 'S', 'T' => 'F', 'J' => 'P');
        
        // Filter personality/preference values from array.        
        $result = array();
        foreach($arr as $key => $val)
        {
            // Check whether key starts with 'personality'
            if(substr_compare($subject, $key, 0, $slen) === 0)
            {
                // Component is I, N, T or J.
                $component = $key[$slen];
                if($val < 0.5)
                {
                    // Component is not dominant, give opposite one instead.
                    $component = $opposites[$component];
                    
                    // Invert value.
                    $val = 1 - $val;
                }
                
                // Add to result.
                $result[$component] = $val;
            }
        }
        
        return $result;
    }
}<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class About extends CI_Controller 
{

    public function index($msg = null)
	{
		$this->load->view('header',array("pagename" => "About"));
        $this->load->view('loginbox');
		$this->load->view('nav');
        $this->load->view('footer');
	}
	
}
?><file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

include_once('profilebrowser.php');
class Search extends ProfileBrowser
{
    // Returns null when input is valid, or an error message otherwise.
    private function _validateInput($input)
    {
        // Generic error message.
        $genericError = 'Something went wrong while processing your search query.';
        
        // Check whether keys are correct.
        $required = array('ownGender', 'genderPref', 'ownAge', 'minAge', 'maxAge', 'attitude',
                          'perceiving', 'judging', 'lifestyle');
        if(sort(array_keys($input)) != sort($required))
        {
            return $genericError;
        }
        
        if(!in_array($input['ownGender'], array('male', 'female')))
        {
            return $genericError;
        }
        
        if(!in_array($input['genderPref'], array('male', 'female', 'either')))
        {
            return $genericError;
        }
        
        foreach (array($input['ownAge'], $input['minAge'], $input['maxAge']) as $age)
        {
            if($age < 18 || $age > 122)
            {
                return "Please enter a valid age: a number between 18 and 122.";
            }
        }
        
        if($input['minAge'] > $input['maxAge'])
        {
            return "Maximal age should not be lower than minimal age.";
        }
        
        return null;
    }
    
    // Displays the matching users. Or nothing, if no user is logged in.
    public function matching()
    {
    	// Confirm whether the user is logged in.
		if(!$this->authentication->userLoggedIn())
		{
			throw new Exception('User is not logged in.');
		}
    	
    	$this->load->model('Matching', 'matching');
    	
    	// Header and navigation bar.
        $this->load->view('header');
        $this->load->view('nav');
        $this->load->view('loginbox');
        
        $current = $this->authentication->currentUserId();
        if($current !== null)
        {
        	$list = $this->matching->matchingList($current);
        	$this->userBrowser($list);
        }
        
        // Footer.
        $this->load->view('footer');
    }
    
    /**
     * Shows the users that have a certain like status, along with three buttons to select which
     * to view.
     * 
     * @param string $likeStatus Can be 'liking' (for others liking the user), 'liked' 
     *                           (for those being liked) and 'mutual' (when both users like each 
     *                           other). If empty, only the selection box is shown.
     */
    public function likes($likeStatus = '')
    {
        // Assert the user is logged in.
        if(!$this->authentication->userLoggedIn())
        {
            echo 'Access denied.';
            return;
        }
        
        // Load user model.
        $this->load->model('User', 'user');
        
        // Reformat input to be usable with model-function.
        $status = null;
        if($likeStatus == 'liking')
        {
            $status = array(false, true);
        }
        else if($likeStatus == 'liked')
        {
            $status = array(true, false);
        }
        else if($likeStatus == 'mutual')
        {
            $status = array(true, true);
        }
        
        // Header and navigation bar.
        $this->load->view('header');
        $this->load->view('nav');
        $this->load->view('loginbox');
        
        // Likes selection box.
        $this->load->view('likebox');
        
        // View results if something has been selected.
        if($status !== null)
        {
            // Get users to show.
            $users = $this->user->usersWithLikeStatus($status);
            
            // Use browser with id's.
            $this->userBrowser($users);
        }
        
        // Footer.
        $this->load->view('footer');
    }
    
    public function index()
    {        
        // Header and navigation bar.
        $this->load->view('header', array("pagename" => "Search"));
        $this->load->view('nav');
        $this->load->view('loginbox');
        
        // Fetch search query data, if present.
        $input = $this->input->post();
        
        $error = null;
        $doSearch = false;
        $data;
        
        // If something in POST, it means a search query has been done.
        if($input && count($input) > 0)
        {
            // Validate input.
            $error = $this->_validateInput($input);
            
            if($error === null)
            {
                $doSearch = true;
            }
            
            // Specify error and data that should be left filled in form from last query.
            $data = array(
                      'error'   => $error,
                      'sFemale' => $input['ownGender'] == 'female' ? 'selected' : '',
                      
                      'malePref' => $input['genderPref'] == 'male' ? 'selected' : '',
                      'femalePref' => $input['genderPref'] == 'female' ? 'selected' : '',
                      
                      'ownAge' => $input['ownAge'],
                      'minAge' => $input['minAge'],
                      'maxAge' => $input['maxAge'],
                      
                      'sI' => $input['attitude'] == 'I' ? 'selected' : '',
                      'sN' => $input['perceiving'] == 'N' ? 'selected' : '',
                      'sF' => $input['judging'] == 'F' ? 'selected' : '',
                      'sP' => $input['lifestyle'] == 'P' ? 'selected' : '',
                      
                      'brands' => $input['brands']
            ); 
        }
        else
        {
            /*$data = array_fill_keys(
                array('error', 'sFemale', 'malePref', 'femalePref', 'ownAge', 'minAge', 'maxAge',
                      'sI', 'sN', 'sF', 'sP', 'brands'), null);*/
            
            $keys = array('error', 'sFemale', 'malePref', 'femalePref', 'ownAge', 'minAge', 
                          'maxAge', 'sI', 'sN', 'sF', 'sP', 'brands');
            foreach($keys as $key)
            {
                $data[$key] = null;
            }
        }

        
        // Display the search form with filled in values and possible error.
        $this->parser->parse('searchform', $data);
        
        // If a search is to be done. Do so and display first six profiles.
        if($doSearch)
        {
            // Load search model.
            $this->load->model('SearchModel', 'search');
            
            // Perform the search operation.
            $ids = $this->search->search($input);
                        
            $this->userBrowser($ids);
        }
        
        // Footer.
        $this->load->view('footer');
    }
}
<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Datingsite extends CI_Controller 
{

    public function index($msg = null)
	{
        $this->load->model('user','',true);
        $this->load->library('personality');
        $this->load->helper('html');
		$this->load->helper('form');
		
		$this->load->view('header');
        $this->load->view('loginbox');
		$this->load->view('nav');
        
        
        $this->load->view('content/showcase');
        $data = array ( 'profileType'	=> 'small',
        				'profiles'		=> $this->_makeProfiles());
        $this->load->view('content/profile', $data);
        $this->load->view('footer');
	}
	
	public function updateUser()
	{
		$this->load->library('form_validation');
		
		if ($this->form_validation->run() == false) {
			$this->load->view('myform');
		}
		else {
			$this->load->view('formsuccess');
		}
		
		$this->load->helper('html');
		$data = array('user' => array('text' => 'Smoked yah!'));
		$this->load->view('content/testView', $data);
	}
	
	private function _buildUser($userId) {
		$user = $this->user->load($userId);
		$user['personality'] = $this->personality->dominantPersonalityComponents($user);
		$user['preference'] = $this->personality->dominantPersonalityComponents($user, true);
		return $user;
	}
	
    /*  Makes the small profiles for the homepage
    */
    private function _makeProfiles()
    {
        //$this->load->library('parser');
        //$data = array(
        $profiles = array(1 => $this->user->getUserProfile(1),
                          2 => $this->user->getUserProfile(2), 
                          3 => $this->user->getUserProfile(3), 
                          4 => $this->user->getUserProfile(4), 
                          5 => $this->user->getUserProfile(5),
                          6 => $this->user->getUserProfile(6));
        //$this->parser->parse('content/profiles-small', $data);
        return $profiles;
    }
}    
<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Picture
{
    // Dimensions of picture thumbnails.
    const THUMBNAIL_WIDTH =  125,
          THUMBNAIL_HEIGHT = 150;
    
    /**
     * Processes an uploaded picture by resizing it and moving it from the uploads to the the 
     * picture folder. The uploaded file will be deleted afterwards.
     * 
     * @param string $picFile The path of the uploaded image, including file name.
     * 
     * @return string The filename of the picture, excluding its path (which is 'img/pictures').
     * 	              On failure, null is returned.
     */
    public function process($picFile)
    {	    
	    // Copy 250x300 version of uploaded picture to img-folder, using GD.
	    $source = imagecreatefromjpeg($picFile);
	    list($sourceWidth, $sourceHeight) = getimagesize($picFile);
	    $dest = imagecreatetruecolor(self::THUMBNAIL_WIDTH, self::THUMBNAIL_HEIGHT);
	    
	    $success = imagecopyresampled($dest, $source, 0, 0, 0, 0, 
	                  self::THUMBNAIL_WIDTH, self::THUMBNAIL_HEIGHT, $sourceWidth, $sourceHeight);
	    
	    if($success)
	    {
	        // Generate picture filename (16 random lowercase characters + .jpg).
	        $picture = '';
	        for($i = 0; $i < 16; ++$i)
	        {
	            $picture .= chr(rand(ord('a'), ord('z')));
	        }
	        $picture .= '.jpg';
	        
	        // Save image.
	        $success = imagejpeg($dest, 'img/pictures/' . $picture);
	    }
	    
	    // Delete image upload.
	    unlink($picFile);
	    
	    if($success)
	    {
	        // Add Image filename to data.
	        return $picture;
	    }
	    else
	    {
	        return null;
	    }
	}
	
	/**
	 * Uploads a file in the POST-body and processes it.
	 * 
	 * @return string Same as the function process($picFile).
	 */
	public function uploadAndProcess($fieldname = 'picture')
	{
	    // Configure and load the upload library.
	    $config = array(
	    	'upload_path' => './uploads/',
    	    'allowed_types' => 'gif|jpg|png',
    	    'max_size' => '1000',
    	    'allowed_types' => 'jpg' // Only jpegs are supported.
	    );

	    $ci =& get_instance();
	    $ci->load->library('upload');
	    $ci->upload->initialize($config);
	    
	    // Do the actual upload.
	    $success = $ci->upload->do_upload($fieldname);
	    
	    if($success)
	    {
	        $data = $ci->upload->data();
	        
	        // Process the uploaded file.
	        return $this->process($data['full_path']);
	    }
	    else
	    {
	        //echo $ci->upload->display_errors();
	        return null;
	    }
	}
}<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class User extends CI_Model
{
    /**
     * Indicates whether anonymous users can view the content of a certain column. Columns not in
     * this array shouldn't be visible at all. Besides these, viewing brand preferences also does
     * not require being logged in.
     */
    private $visibility = array(
        'username'      => true,
        'email'         => false,
        'firstName'     => false,
        'lastName'        => false,
        'gender'        => true,
        'birthdate'        => true,
        'description'    => true,
        'minAgePref'    => true,
        'maxAgePref'    => true,
        'genderPref'    => true,
        'personalityI'    => true,
        'personalityN'    => true,
        'personalityT'    => true,
        'personalityJ'    => true,
        'preferenceI'    => true,
        'preferenceN'    => true,
        'preferenceT'    => true,
        'preferenceJ'    => true,
        'picture'        => false
    );
    
    /**
     * Returns the columns visibile to the currently active user.
     */
    private function visibleColumns()
    {
        $login = $this->authentication->userLoggedIn();
        if($login)
        {
            return array_keys($this->visibility);
        }
        else
        {
            $result = array();
            foreach($this->visibility as $col => $visible)
            {
                if($visible)
                {
                    $result[] = $col;
                }
            }
            return $result;
        }
    }
    
    /**
     * Loads the preferred brands of a user.
     * 
     * @param int $userId The ID of the user.
     * 
     * @return array(string) The preferred brands of this user. 
     */
    private function loadUserBrands($userId)
    {
        $result = $this->db->select('ub.brandName')
                           ->from('UserBrands ub')
                           ->join('Brands b', 'ub.brandName = b.brandName', 'left')
                           ->where(array('userId' => $userId))
                           ->get()->result_array();
        
        if(count($result) == 0)
        {
            throw new Exception('User not found or no brands associated.');
        }
        
        $brandNames = array();
        foreach($result as $row)
        {
            $brandNames[] = $row['brandName'];
        }
        return $brandNames;
    }
    
    private function removeInvisibleColumns($data)
    {
        $login = $this->authentication->userLoggedIn();
        
        $result = array();
        foreach($data as $key => $val)
        {
            if(isset($this->visibility[$key]))
            {
                if($login || $this->visibility[$key])
                {
                    $result[$key] = $val;
                }
            }
        }
    
        return $result;
    }
    
    /**
     * Loads the properties of a user visible to the current user. These properties are the values
     * of columns in the Users-table plus an entry 'brands' containing an array of strings 
     * representing brand names preferred by the user.
     * 
     * @param int           $userId     The identifier of the user to load.
     * @param array(string) $properties The properties of the user to load. If null,
     *                                  all accessible properties are loaded.
     * 
     * @return array(string => mixed) An associative array of user properties and values.
     */
    public function load($userId, $properties = null)
    {
        // Determine which columns to select.
        if($properties !== null)
        {
            // Filter by wanted properties.
            $columns = $this->removeInvisibleColumns($properties);
        }
        else
        {
            $columns = $this->visibleColumns();
        }
        
        $result = $this->db->select($columns)
                           ->from('Users')
                           ->where(array('userId' => $userId))
                           ->get()->row_array();
        
        if(!$result)
        {
            throw new Exception('User not found.');
        }
        
        $user = $result;
        
        // Add brands if wanted.
        if($properties === null || in_array('brands', $properties))
        {
            $user['brands'] = $this->loadUserBrands($userId);
        }
        
        return $user;
    }
    
    public function updateSelf($newprops)
    {
        // Determine which user is logged in.
        $userId = $this->authentication->currentUserId();
        
        if($userId === null)
        {
            throw new Exception('User is not logged in.');
        }
        
        //Start a transaction.
        $this->db->trans_start();
                
        // Values in Users table to change are all new properties that are also visible columns.
        $data = $this->removeInvisibleColumns($newprops);
        
        // Do update.
        $this->db->where(array('userId' => $userId))
                 ->update('Users', $data);
        
        // If requested, update brands.
        if(isset($newprops['brands']))
        {
            // First simply remove this user's brands.
            $this->db->delete('UserBrands', array('userId' => $userId));
            
            // Now insert new brands.
            foreach($newprops['brands'] as $brand)
            {
                $this->db->insert('UserBrands',
                                array('userId'    => $userId,
                                      'brandName' => $brand));
            }
            //$this->db->insert_batch('UserBrands', $brandList);
        }
                 
        // Complete transaction.
        $this->db->trans_complete();
    }
    
    // Creates a salted SHA-1 hash of a password.
    private function hashPassword($password)
    {
        // Load security helper.
        $this->load->helper('security');
        
        // The randomly generated key we specified for session encryption is also perfectly 
        // suitable to be a salt.
        // Also add a smiley face wearing a hat, which is incredibly important.
        $salt = $this->config->item('encryption_key') . '<:)';
        
        return do_hash($password . $salt);
    }
    
    /**
     * Create a new user.
     * 
     * @param array(string => mixed) $data     Values to be inserted into the Users table, except for
     *                                         the password hash. Data should be already validated!
     * @param string                 $password The user's password. Will be hashed by this function.
     * @param array(string)          $brands   List of preferred brands by this user. There should
     *                                         be at least one.
     *                                         
     * @return int The ID of the newly created user.
     */
    public function createUser($data, $password, $brands)
    {        
        // Hash password.
        $data['passwordHash'] = $this->hashPassword($password);
        
        //Start a transaction.
        $this->db->trans_start();
        
        // Insert new user into table.
        $this->db->insert('Users', $data);
        
        // Add preferred brands.
        $userId = $this->db->insert_id();
        foreach($brands as $brand)
        {
            $this->db->insert('UserBrands',
                                array('userId'    => $userId,
                                      'brandName' => $brand));
        }
        
        // Complete transaction.
        $this->db->trans_complete();
        
        return $userId;
    }
    
    /**
     * Look up a user with a certain e-mail/password combination. To be used for logging in.
     * 
     * @param string  $email     An e-mail address.
     * @param string  $password  A password, yet unhashed.
     * @param string &$username  If a user is found, its username is stored in here.
     * 
     * @return int The userId of this user, or null if no such user exists.
     */
    public function lookup($email, $password)
    {
        // Hash the password.
        $hash = $this->hashPassword($password);
        
        // Query the user.
        $result = $this->db->select('userId')->from('Users')
                           ->where(array('email'=> $email, 'passwordHash' => $hash))
                           ->get()->row();
        
        if(count($result) > 0)
        {
            return $result->userId;
        }
        else
        {
            return null;
        }
    }
    
    /**
     * Delete the currently active user.
     */
    public function deleteSelf()
    {
        // Determine which user is logged in, if any.
        $userId = $this->authentication->currentUserId();
        
        if($userId === null)
        {
            throw new Exception('User is not logged in.');
        }
        
        // Delete this user.
        $this->deleteUser($userId);
    }
    
    public function deleteUser($userId)
    {
        $this->db->trans_start();
        
        // Also delete references to user in Likes and UserBrands.
        $this->db->or_where(array('userLiking' => $userId, 'userLiked' => $userId))
                 ->delete('Likes');
        $this->db->delete('UserBrands', array('userId' => $userId));
        
        // Delete user itself.
        $this->db->delete('Users', array('userId' => $userId));
        
        $this->db->trans_complete();
    }
    
    /**
     * Makes the current user 'like' another user.
     * 
     * @param int $likedUser ID of the user to like.
     */
    public function like($likedUser)
    {
        // Determine which user is logged in, if any.
        $userId = $this->authentication->currentUserId();
        
        if($userId === null)
        {
            throw new Exception('Not logged in.');
        }
        if($userId == $likedUser)
        {
            throw new Exception("Can't like yourself.");
        }
        
        // For learning, retrieve the current alpha setting.
        $alpha = $this->db->select('alpha')->from('Configuration')
                          ->get()->row()->alpha;
        $beta = 1 - $alpha;
        
        // Start a transaction.
        $this->db->trans_start();
        
        // Determine the new personality preference of this user.
        $ownPref = $this->db->select(array('preferenceI', 'preferenceN', 
                                           'preferenceT', 'preferenceJ'))
                            ->from('Users')
                            ->where('userId', $userId)
                            ->get()->row_array();
        $likedPers = $this->db->select(array('personalityI', 'personalityN', 
                                             'personalityT', 'personalityJ'))
                              ->from('Users')
                              ->where('userId', $likedUser)
                              ->get()->row_array();
        $newPref = array();
        foreach(array('I', 'N', 'T', 'J') as $d)
        {
            $newPref["preference$d"] = $alpha * $ownPref["preference$d"]
                                     + $beta * $likedPers["personality$d"];
        }
        
        // Now update it.
        $this->db->where('userId', $userId)
                 ->update('Users', $newPref);
        
        // Add to Likes table (unique and foreign key constraints enforce $likedUser is an
        // existing user id and that there are no duplicates).
        $this->db->insert('Likes', array(
                                      'userLiking' => $userId,
                                      'userLiked'  => $likedUser
                                    ));
        
        // Complete transaction.
        $this->db->trans_complete();
    }
    
    /**
     * Indicates the 'like-status' between the current user and another one. 
     * @param int $otherUser The other user.
     * 
     * @return array(bool) An array, containing two booleans A and B, where A is true iff the 
     *                     current user has liked the other one and B is true iff the other user 
     *                     likes this one.
     */
    public function getLikeStatus($otherUser)
    {
        $userA = $this->authentication->currentUserId();
        $userB = $otherUser;
        
        if($userA === null)
        {
            throw new Exception('User is not logged in.');
        }
        
        // Query whether this user likes the other one.
        $likeAB = (bool) $this->db->from('Likes')
                              ->where('userLiking', $userA)
                              ->where('userLiked', $userB)
                              ->get()->row();
                           
        // Query whether the other user likes this one.
        $likeBA = (bool) $this->db->from('Likes')
                                     ->where('userLiking', $userB)
                                  ->where('userLiked', $userA)
                                  ->get()->row();
                           
        
        return array($likeAB, $likeBA);
    }
    
    /**
     * Get the id's of all users with a certain like status towards the current user.
     * 
     * @param array(bool) $status Formatted in the same way as the result of getLikeStatus(..).
     *                            An empty array is returned when this is [false, false].
     * 
     * @return array(int) The id's of the users with this status.
     */
    public function usersWithLikeStatus($status)
    {
        // Get current user.
        $userId = $this->authentication->currentUserId();
        
        if($userId === null)
        {
            throw new Exception('User is not logged in.');
        }
        
        // Start a transaction.
        $this->db->trans_start();
        
        if($status[0])
        {
            // Find others liked by the current one.
            $liked = $this->db->select('userLiked AS userId')->from('Likes')
                              ->where('userLiking', $userId)
                              ->get()->result();
            //Only examine id's.
            foreach($liked as &$id)
            {
                $id = $id->userId;
            }
        }
        
        if($status[1])
        {
            // Find others liking the current one.
            $liking = $this->db->select('userLiking AS userId')->from('Likes')
                               ->where('userLiked', $userId)
                               ->get()->result();
            
            foreach($liking as &$id)
            {
                $id = $id->userId;
            }
        }
        
        // Commit transaction.
        $this->db->trans_complete();
        
        // Determine what to return.
        $result = array();
        if($status[0] && $status[1])
        {
            // Combine liked and liking.
            $result = array_intersect($liked, $liking);
        }
        else if($status[0])
        {
            $result = $liked;
        }
        else if($status[1])
        {
            $result = $liking;
        }
        
        
        return $result;
    }
    
    public function getRandomUsers($amount)
    {    	
    	// Do query.
    	$result = $this->db->select('userId')
    					   ->from('Users')
    					   ->order_by('userId', 'random')
    					   ->limit($amount)
    					   ->get()->result();
    		          
    	// Only return id's.
    	foreach($result as &$row)
    	{
    		$row = $row->userId;
    	}

    	return $result;
    }
    
	public function getUserProfile($userId)
    {
    	// The library for calculating the personality type is loaded
    	$this->load->library('personality');
    	
    	// Load the user data into the profile
    	$profile = $this->user->load($userId);
    	
    	$profile['userId'] = $userId;
    	
    	// Get the dominant personality and preference and add them to the profile
		$personality = $this->personality->dominantPersonalityComponents($profile);
		$preference = $this->personality->dominantPersonalityComponents($profile, true);
		$profile['personality'] = "";
		$profile['preference'] = "";
		// For each dominant personality, add the key to the personality in the profile 
		foreach($personality as $key => $value) {
			$profile['personality'] .= $key;
		}
		// For each dominant preference, add the key to the preference in the profile
		foreach($preference as $key => $value) {
			$profile['preference'] .= $key;
		}
		
		// If the current user is logged in and watching someone else's profile,
		// add the likestatus with that person to the profile.
		$userLoggedIn = $this->authentication->currentUserId();
		if($userLoggedIn != null && $userLoggedIn != $userId) {
			$profile['likestatus'] = $this->getLikeStatus($userId);
		}
		
		return $profile;
    }
}<file_sep><!DOCTYPE html>
<html>
<head>
    <title>LuciferMaker</title>
    <link rel="stylesheet" type="text/css" href="<?php echo base_url() . '/css/secondStyle.css'; ?>" />
    <script type="text/javascript" src="<?php echo base_url() . '/js/jquery.js'; ?>"> </script>
</head>
<body>
    <div id='container'>
    <header>
        <h1>
            LuciferMaker, matchmaking since 2012
        </h1>
    </header><file_sep><div id="loginbox">

<?php 
$home = base_url() . 'index.php';

// Get CodeIgniter object.
$ci =& get_instance();

// Check which user is logged in.
$uname = $ci->authentication->currentUserName();

if($uname !== null)
{
    // User is logged in, show a welcome message with logout option.
    echo '<p><strong>Welcome, ' . form_prep($uname) . '!</strong>'
      .  ' (<a href="' . "$home/home/logout" . '">logout</a>)';
    
    // Show option for deleting the account.
    echo '<p><strong id="deleter">Delete account</strong></p>';
}
else
{
	// If nobody is logged in, show login form.
    echo '<h3>Login or <a href="' . "$home/register" . '">Register</a> </h3>';
 
    echo form_open('home/login')
      .  form_label('E-mail: &nbsp&nbsp&nbsp&nbsp&nbsp', 'email')
      .  form_input('email')
      .  '<br/>'
      .  form_label('Password: ', '<PASSWORD>')
      .  form_password('<PASSWORD>')
      .  form_submit('submit', 'Login')
      .  form_close();
}

?>    	
</div>

<script type="text/javascript">
// Add mouse listener to delete link. 

$('#deleter').click(function()
{
	var ok = confirm('You are about to delete your account. Are you absolutely sure?');

	if(ok)
	{
		// Send delete request. 
		$.post('<?php echo "$home/home/delete"; ?>',
			   {sure: 'yes'},
			   function()
			   {
					// Refresh page when done. 
					window.location.reload();
			   });
	}
});
</script><file_sep><!DOCTYPE html>
<html>
<head>
    <title>Risa - <?php
                      if(isset($pagename))
                      { 
                    	echo $pagename;
                      }
                  ?>
	</title>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <link rel="stylesheet" type="text/css" href="<?php echo base_url() . '/css/style.css'; ?>" />
    <script type="text/javascript" src="<?php echo base_url() . '/js/jquery.js'; ?>"> </script>
</head>
<body>
	<div id='container'>
    <header>
        <h1>
            Risa, matchmaking since 2012
        </h1>
    </header><file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class AdminPanel extends CI_Controller
{
    // A simple validator that checks whether the input has the right format.
    // Returns null if it validates, or a string containing an error if it doesn't.
    // This function also removes excess keys from the input array.
    private function _validateInput(&$input)
    {
        $s = $input['similarityMeasure'];
        $x = $input['xFactor'];
        $a = $input['alpha'];
        
        if($s === null || $s < 0 || $s > 3)
        {
            return 'Invalid similarity measure.';
        }
        if($x === null || $x < 0 || $x > 1)
        {
            return 'Invalid X-factor.';
        }
        if($a === null || $a < 0 || $a > 1)
        {
            return 'Invalid alpha factor.';
        }
        
        // Rebuild array to remove unnessecary input.
        $input = array('similarityMeasure' => $s,
                       'xFactor'           => $x,
                       'alpha'             => $a);
        
        // Validation passed.
        return null;
    }
    
    public function index()
    {
        // Only admins should have access to this page.
        $this->authentication->assertAdministrator();
        
        // Load configuration model.
        $this->load->model('Configuration', 'conf');
        
        // Load form helper.
        $this->load->helper('form');
        
        $error = null;
        
        // Check whether configurations have been changed.
        $input = $this->input->post();
        if($input !== false)
        {
            // Validate input.
            $error = $this->_validateInput($input);

            if($error === null)
            {
                // Update configuration.
                $this->conf->save($input);
            }
        }
        
        // Load current configuration.
        $data = $this->conf->load();
        
        // Add error.
        $data['error'] = $error;
        
        // Header.
        $this->load->view('header', array('pagename' => 'Configuration'));
        $this->load->view('nav');
        $this->load->view('loginbox');
        
        // Show admin panel view with data.
        $this->load->view('adminpanel', $data);
        
        // Footer.
        $this->load->view('footer');
    }
}
<file_sep>BEGIN TRANSACTION;

PRAGMA encoding = "UTF-8";
PRAGMA foreign_keys = 1;

CREATE TABLE "Users"
(
    "userId" integer NOT NULL,
    "username" varchar(30) NOT NULL,
    "email" varchar(256) NOT NULL,
    "passwordHash" char(40) NOT NULL, -- For SHA-1 hashes.
    
    "firstName" varchar(30) NOT NULL,
    "lastName" varchar(50) NOT NULL,
    "gender" bool NOT NULL, -- 0: male, 1: female
    "birthdate" date NOT NULL,
    "description" varchar(300) NOT NULL,
    "minAgePref" integer NOT NULL,
    "maxAgePref" integer NOT NULL,
    "genderPref" bool, -- NULL in case of bisexuality
    
    -- Personality values, each is an integer between 0 and 100; E,S,F and P can be derived
    "personalityI" integer NOT NULL,
    "personalityN" integer NOT NULL,
    "personalityT" integer NOT NULL,
    "personalityJ" integer NOT NULL,
    
    -- Personality preferences
    "preferenceI" integer NOT NULL,
    "preferenceN" integer NOT NULL,
    "preferenceT" integer NOT NULL,
    "preferenceJ" integer NOT NULL,
    
    -- Contains the filename of an uploaded picture
    "picture" varchar(20),
    
    "admin" bool NOT NULL DEFAULT '0',
    
    PRIMARY KEY("userId"),
    UNIQUE("username"),
    UNIQUE("email")
);

CREATE TABLE "Brands"
(
    "brandName" varchar(30) NOT NULL,
    
    PRIMARY KEY ("brandName")
);

CREATE TABLE "UserBrands"
(
    "userId" integer NOT NULL,
    "brandName" varchar(30) NOT NULL,
    
    FOREIGN KEY ("userId")
      REFERENCES "Users"
       ON DELETE CASCADE,
    FOREIGN KEY ("brandName")
      REFERENCES "Brands"
        ON DELETE CASCADE,
        
    UNIQUE ("userId", "brandName")
);

CREATE TABLE "Likes"
(
    "userLiking" integer NOT NULL,
    "userLiked" integer NOT NULL,
    
    UNIQUE("userLiking", "userLiked"),
    FOREIGN KEY ("userLiking")
      REFERENCES "Users"
        ON DELETE CASCADE,
    FOREIGN KEY ("userLiked")
      REFERENCES "Users"
        ON DELETE CASCADE
);

-- Table should have exactly one row.
CREATE TABLE "Configuration"
(
    "similarityMeasure" integer NOT NULL, -- 0: Dice's, 1: Jaccard's, 2: cosine, 3: overlap
    "xFactor" float NOT NULL,
    "alpha" float NOT NULL
);

-- Default configuration.
INSERT INTO "Configuration" ("similarityMeasure", "xFactor", "alpha")
    VALUES (0, 0.5, 0.5);
    

-- Session table. Automaticallt managed by CodeIgniter's session class.
CREATE TABLE "Sessions"
(
    "session_id" varchar(40) DEFAULT '0' NOT NULL,
	"ip_address" varchar(16) DEFAULT '0' NOT NULL,
	"user_agent" varchar(120) NOT NULL,
	"last_activity" unsigned integer(10) DEFAULT 0 NOT NULL,
	"user_data" text NOT NULL,
	
	PRIMARY KEY (session_id)
);

CREATE INDEX "last_activity_idx"
    ON "Sessions" ("last_activity");
    
-- Admin user.
INSERT INTO "Users" 
    VALUES (
        1,
        'admin',
        '<EMAIL>',
        '105c075b400c0ac2ebacaf91da608e3f947a22e0',
        'Admin',
        'The Admin',
        0,
        '1991-02-06',
        'I am the almighty administrator of this website! Bow and tremble before my mighty administrative capabilities!',
        18,
        25,
        1,
        50, 50, 50, 50, 50, 50, 50, 50,
        NULL,
        1
    );


-- Brands.

-- Top 50 most popular brands on Twitter on March 7 2011. 
-- Source: http://tweetedbrands.com/

INSERT INTO "Brands" ("brandName") VALUES ('Twitter');
INSERT INTO "Brands" ("brandName") VALUES ('Youtube');
INSERT INTO "Brands" ("brandName") VALUES ('Facebook');
INSERT INTO "Brands" ("brandName") VALUES ('iPhone');
INSERT INTO "Brands" ("brandName") VALUES ('Google');
INSERT INTO "Brands" ("brandName") VALUES ('Apple');
INSERT INTO "Brands" ("brandName") VALUES ('Android');
INSERT INTO "Brands" ("brandName") VALUES ('Blackberry');
INSERT INTO "Brands" ("brandName") VALUES ('Myspace');
INSERT INTO "Brands" ("brandName") VALUES ('Amazon');
INSERT INTO "Brands" ("brandName") VALUES ('BBC');
INSERT INTO "Brands" ("brandName") VALUES ('Nokia');
INSERT INTO "Brands" ("brandName") VALUES ('MTV');
INSERT INTO "Brands" ("brandName") VALUES ('CNN');
INSERT INTO "Brands" ("brandName") VALUES ('Microsoft');
INSERT INTO "Brands" ("brandName") VALUES ('Starbucks');
INSERT INTO "Brands" ("brandName") VALUES ('Yahoo!');
INSERT INTO "Brands" ("brandName") VALUES ('eBay');
INSERT INTO "Brands" ("brandName") VALUES ('Nike');
INSERT INTO "Brands" ("brandName") VALUES ('Disney');
INSERT INTO "Brands" ("brandName") VALUES ('Sony');
INSERT INTO "Brands" ("brandName") VALUES ('Mashable');
INSERT INTO "Brands" ("brandName") VALUES ('Samsung');
INSERT INTO "Brands" ("brandName") VALUES ('BP');
INSERT INTO "Brands" ("brandName") VALUES ('Ford');
INSERT INTO "Brands" ("brandName") VALUES ('Canon');
INSERT INTO "Brands" ("brandName") VALUES ('Motorola');
INSERT INTO "Brands" ("brandName") VALUES ('Guardian');
INSERT INTO "Brands" ("brandName") VALUES ('KFC');
INSERT INTO "Brands" ("brandName") VALUES ('Toyota');
INSERT INTO "Brands" ("brandName") VALUES ('Techcrunch');
INSERT INTO "Brands" ("brandName") VALUES ('Coca-Cola');
INSERT INTO "Brands" ("brandName") VALUES ('Dell');
INSERT INTO "Brands" ("brandName") VALUES ('Gucci');
INSERT INTO "Brands" ("brandName") VALUES ('BMW');
INSERT INTO "Brands" ("brandName") VALUES ('British Airways');
INSERT INTO "Brands" ("brandName") VALUES ('Pepsi');
INSERT INTO "Brands" ("brandName") VALUES ('Intel');
INSERT INTO "Brands" ("brandName") VALUES ('McDonald');
INSERT INTO "Brands" ("brandName") VALUES ('Honda');
INSERT INTO "Brands" ("brandName") VALUES ('Adidas');
INSERT INTO "Brands" ("brandName") VALUES ('IBM');
INSERT INTO "Brands" ("brandName") VALUES ('Panasonic');
INSERT INTO "Brands" ("brandName") VALUES ('T-Mobile');
INSERT INTO "Brands" ("brandName") VALUES ('Spotify');
INSERT INTO "Brands" ("brandName") VALUES ('Lego');
INSERT INTO "Brands" ("brandName") VALUES ('Digg');
INSERT INTO "Brands" ("brandName") VALUES ('Mercedes');
INSERT INTO "Brands" ("brandName") VALUES ('IKEA');
INSERT INTO "Brands" ("brandName") VALUES ('VW');

COMMIT;
<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class ProfileBrowser extends CI_Controller
{
	
	function __construct() {
        parent::__construct();
    }
	
	public function displayProfiles(/* ... */)
    {
        $userIds = func_get_args();
        
        if(count($userIds) == 1 && is_array($userIds[0]))
        {
            $userIds = $userIds[0];
        }
        
        // There shouldn't be more than six profiles to display.
        if(count($userIds) > 6)
        {
            throw new Exception("Can't display more than six profiles");
        }
        
        // Output nothing if there are no id's.
        if(count($userIds) == 0)
        {
            return;
        }
        
        // Load the user model.
        $this->load->model('User', 'user');
        
        // Load profiles.
        $profiles = array();
        foreach($userIds as $id)
        {
            $profiles[] = $this->user->getUserProfile($id);
        }
        // The profiles are placed in the data array and the profile type is set.
        $data['profiles'] = $profiles;
        $data['profileType'] = 'small';
        // Display profile overviews.
        $this->parser->parse('content/profile', $data);
    }
    
    /**
     * Prints a browser for user profiles.
     * 
     * @param array(int) $ids The id's of the users that should be browsable.
     */
    public function userBrowser($ids)
    {
        $ids = array_values($ids);
        
        // Display the first six results (or less, if there aren't as much).
        $toDisplay = array_splice($idscopy = $ids, 0, min(6, count($ids)));
        $this->displayProfiles($toDisplay);
            
        // Add the search browser and give it all the found id's.
        // We restrict the number of id's send to 600 to prevent generating a gigantic javascript
        // file when there are a lot of matches. 600 is more than enough since I doubt many people
        // will click 'next' more than a hundred times.
        $data = array('ids' => array());
        for($i = 0; $i < min(600, count($ids)); ++$i)
        {
            $data['ids'][] = array('id' => $ids[$i]);
        }
            
        $this->parser->parse('searchbrowser', $data);
    }
}
<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Register extends CI_Controller 
{

	// After succesfully validating a filled in form, use the POST data to add a new user to the
	// database.
    private function _registerUser()
	{
	    // Load libraries and models.
	    $this->load->library('picture');
	    $this->load->model('user');
	    
	    // Fetch input.
	    $input = $this->input->post();
	    
	    // This will be contain the parsed data as added to the user model.
	    $data = array();
	    
	    // Values that can be directly put to the data array (after a rename of their keys).
	    $rename = array(
	                'username' => 'username',
	                'firstName' => 'firstName',
	                'lastName' => 'lastName',
	                'email' => 'email',
	                'gender' => 'gender',
	                'description' => 'description',
	                'ageprefmin' => 'minAgePref',
	                'ageprefmax' => 'maxAgePref',
	                'birthdate' => 'birthdate'
	              );
	    foreach($rename as $key => $newkey)
	    {
	        $data[$newkey] = $input[$key];
	    }
	    
	    // Get password (will be hashed by User::createUser).
	    $password = $input['<PASSWORD>'];
	    
	    // Parse gender preference.
	    $data['genderPref'] = $input['genderPref'] == 2 ? null : (int) $input['genderPref'];
	    
	    // Parse brand preferences.
	    $brands = $input['brandpref'];
	    
	    // Process uploaded image. 'picture' will be set to null if none are specified.
	    $data['picture'] = $this->picture->uploadAndProcess();
	    
	    // Determine personality from questionnaire.
	    // Also set initial personality preference to the opposite of that.
	    {
	        // Parse answers.
	        $answers = array(); 
	        for($q = 1; $q <= 19; ++$q)
	        {
	            $answers[$q] = $input["question$q"];
	        }
	        
	        // E versus I.
	        $e = 50;
            for($i = 1; $i <= 5; ++$i)
            {
                if($answers[$i] == 'A')
                {
                    $e += 10;
                }
                else if($answers[$i] == 'B')
                {
                    $e -= 10;
                }
            }
            $data['personalityI'] = 100 - $e;
            $data['preferenceI'] = $e;
            
            // N versus S.
            $n = 50;
            for($i = 1; $i <= 5; ++$i)
            {
                if($answers[$i] == 'A')
                {
                    $n += 12.5;
                }
                else if($answers[$i] == 'B')
                {
                    $n -= 12.5;
                }
            }
            $data['personalityN'] = (int) $n;
            $data['preferenceN'] = 100 - (int) $n;
            
            // T versus F.
            $t = 50;
            for($i = 1; $i <= 5; ++$i)
            {
                if($answers[$i] == 'A')
                {
                    $t += 12.5;
                }
                else if($answers[$i] == 'B')
                {
                    $t -= 12.5;
                }
            }
            $data['personalityT'] = (int) $t;
            $data['preferenceT'] = 100 - (int) $t;
            
            // J versus P.
            $j = 50;
            for($i = 1; $i <= 5; ++$i)
            {
                if($answers[$i] == 'A')
                {
                    $j += 8.3333;
                }
                else if($answers[$i] == 'B')
                {
                    $j -= 8.3333;
                }
            }
            $data['personalityJ'] = (int) $j;
            $data['preferenceJ'] = 100 - (int) $j;
	    }
	    
	    // Actually create the user.
	    $this->user->createUser($data, $password, $brands);
	}
	
	public function index()
	{
        $this->load->model('user','',true);
        $this->load->library(array('personality', 'form_validation'));
        $this->load->helper('html');
		$this->load->helper('form');
				
		$this->load->view('header',array("pagename" => "Register"));
        $this->load->view('loginbox');
		$this->load->view('nav');
		
		$config = array(
        		array(
        			'field' => 'username',
        			'label' => 'Username',
        			'rules' => 'required|min_length[4]|max_length[20]' 
        			//TODO controleer dat de naam uniek is. Zie is_unique[table.field]bij form_validation
				),
				array(
        			'field' => 'firstName',
        			'label' => 'Firstname',
        			'rules' => 'required'
				),
				array(
        			'field' => 'lastName',
        			'label' => 'Lastname',
        			'rules' => 'required'
				),
				array(
        			'field' => 'password',
        			'label' => 'Password',
        			'rules' => 'required|min_length[8]|max_length[20]'
				),
				array(
        			'field' => 'passconf',
        			'label' => 'Repeat password',
        			'rules' => 'required|matches[password]|min_length[8]|max_length[20]'
				),
				array(
        			'field' => 'email',
        			'label' => 'Email',
        			'rules' => 'required|valid_email'
				),
				array(
        			'field' => 'gender',
        			'label' => 'Gender',
        			'rules' => 'required'
				),
				array(
        			'field' => 'birthdate',
        			'label' => 'Birthdate',
        			'rules' => 'required|alpha-dash|callback_validateDate'
				),
				array(
        			'field' => 'description',
        			'label' => 'About you',
        			'rules' => ''
				),
				array(
        			'field' => 'genderPref',
        			'label' => 'Gender preference',
        			'rules' => 'required'
				),
				array(
        			'field' => 'ageprefmin',
        			'label' => 'Minimum age',
        			'rules' => 'required|numeric|greater_than[17]|callback_validAgePref[ageprefmax]'
				),
				array(
        			'field' => 'ageprefmax',
        			'label' => 'Maximum age',
        			'rules' => 'required|numeric|less_than[123]'
				),
				array(
                    'field' => 'picture',
                    'label' => 'Upload picture',
                    'rules' => ''				
				)
        );
		
        // The list of brands is loaded from the database
        $this->load->model('brand');
        $brands = $this->brand->getBrands();
        
		foreach($brands as $brand)
	    {
			$config[] = array(
	        				'field' => $brand,
	        				'label' => $brand,
	        				'rules' => '');
	    }
        
		// For each of the 20 personality question, the rule required is set
        for($q = 1; $q <= 19; ++$q)
	    {
	        $config[] =	array(
        					'field' => "question$q",
        					'label' => "Personality question $q",
        					'rules' => 'required'
						);
	    }
	    
	    //TODO Hier gaat het fout.
        //$config = $this->rules_storage->getRules();
        
        // And all the rules are added to the form_validation
        $this->form_validation->set_rules($config);
        // All errors are placed in a div with the error class
        $this->form_validation->set_error_delimiters('<div class="error">', '</div>');
        
        // Check if there is a post result. If there is, the brandpref list is handed
        // to the brandCheckBoxes function for re-populating.
        if(array_key_exists('brandpref',$_POST)) {
        	$brandpref = $_POST['brandpref'];
        }
        else {
        	// If there is a profile, its brandspreferences are used as the re-populate list.
        	$brandpref = null;
        }
        
        $data = array('brands'           => $brands,
                      'brandPreferences' => $brandpref);
        
        // Display the form while it is invalid.
        if ($this->form_validation->run() === false) 
        {
            $this->load->view('content/registerView', $data);
		}
		else 
		{
    	    // The form input is validated, the user is registeredand the succesmessage is displayed.
    		$this->_registerUser();
    		$this->load->view('content/register_succes', $_POST);
		}
        
        $this->load->view('footer');
	}
	
	/**
	 * 
	 * Our own date-validation rule.
	 * @param string $date	The date that will be checked
	 * @return boolean		False if failed
	 */
	public function validateDate($date)
	{
		$dummy = array();
	    $validDate = !!preg_match_all('/[0-9]{4}-[0-9]{2}-[0-9]{2}/', $date, $dummy);
	    $year; $month; $day;
	    sscanf($date, '%D-%D-%D', $year, $month, $day);
	    $thisYear = date('o');
	    $validDate &= $year > $thisYear - 122 && $year <= $thisYear - 18
	               && $month >=  1 && $month <= 12
	               && $day >= 1 && $day <= 31;
	    
	    if(!$validDate) {
	    	if(is_numeric($year) && $year < $thisYear - 122)
	    		$this->form_validation->set_message('validateDate', 
							'If you are older than 122, please contact the Guiness Book of World Records.');
	    	else 
	    		$this->form_validation->set_message('validateDate', 
							'Invalid date');
			return false;
	    }
	    else {
	    	return true;
	    }
	}
	
	/**
	 * 
	 * Checks if ageprefmin is smaller or equal to ageprefmax.
	 * @param int $ageprefmin		The minimum age preference
	 * @param string $ageprefmax	The name for the maximum age preference in the post
	 * @return boolean 				false if fails
	 */
	public function validAgePref($ageprefmin, $ageprefmax)
	{
		if($ageprefmin > $this->input->post($ageprefmax)) {
			$this->form_validation->set_message('validAgePref', 
							'The %s field can not be greater than the Maximum age');
			return false;
		}
		else {
			return true;
		}
	}
	
}	
<file_sep><?php
	
	function boldShow($type, $content, $textblock = false)
	{
		global $profileType;
		
		if($profileType == "form") {
			if($textblock) {
				$content = form_textarea($type, $content, "rows=\"6\" cols=\"80\""); 
			}
			else {
				$content = form_input($type, $content);
			}
		}
		return "<p><b>". htmlentities($type, ENT_QUOTES, "UTF-8"). ":</b> ".
				 htmlentities($content, ENT_QUOTES, "UTF-8"). "</p>";
	}
	
	foreach($user as $key => $val) {
		echo boldShow($key, $val). br(1);
	}
	
?><file_sep><?php

class ViewProfile extends CI_Controller
{
	public function index($msg = null)
	{
		if($this->authentication->userLoggedIn()) {
			$this->showProfile($this->authentication->currentUserId(), 'form');
		}
		else {
			$this->load->view('header',array("pagename" => "Profile"));
	        $this->load->view('loginbox');
			$this->load->view('nav');
			$this->load->view('error',array('error' => "You need to be logged in to edit your profile."));
			$this->load->view('footer');
		}
	}
	
	public function showProfile($userId, $profileType = 'big')
	{
		$this->load->model('user','',true);
		
		$this->load->view('header',array("pagename" => "Profile"));
        $this->load->view('loginbox');
		$this->load->view('nav');
		
		$data = array(	'profileType' => $profileType,
						'profiles' => array($this->user->getUserProfile($userId)));
		$this->load->view('content/profile',$data);
		$this->load->view('footer');
	}
	
	public function like()
	{
		$otherUser = $this->input->post('otherId');

        if($otherUser !== false)
        {
            $this->load->model('User', 'user');
            $this->user->like($otherUser);
        }
	}
}

<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/**
 * Model for queries on brands.
 */
class Brand extends CI_Model
{
    public function __construct()
    {
        parent::__construct();
    }
    
    /**
     * Returns the names of all available brands.
     * 
     * @return array(string)
     */
    public function getBrands()
    {
        $result = $this->db->select('brandName')->from('Brands')->get()->result();
        
        // Only return brand names.
        foreach($result as &$brand)
        {
            $brand = $brand->brandName;
        }
        
        return $result;
    }
}<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class SearchModel extends CI_Model
{
    
    public function search($input)
    {        
        // Query expression to determine the age of a user.
        $age = "(strftime('%Y', 'now') - strftime('%Y', birthdate)
              - (strftime('%j', 'now') < strftime('%j', birthdate)))";
        
        // Build query.
        $query = $this->db->select('u.userId')->from('Users u');

        // Restrict users in result by wanted gender.
        if($input['genderPref'] != 'either')
        {
            $query->where('gender', $input['genderPref'] == 'female');
        }
        
        // Restrict on age.
        $query->where('minAgePref <=', (int) $input['ownAge']);
        $query->where('maxAgePref >=', (int) $input['ownAge']);
        $query->where("$age <=", (int) $input['maxAge']);
        $query->where("$age >=", (int) $input['minAge']);
        
        // 0: male, 1: female.
        $gpref_bool = $input['ownGender'] == 'female' ? '1' : '0';
        
        // Add gender preference to query. 
        $query->where("(genderPref IS NULL OR genderPref = $gpref_bool)");
        
        // Parse brand preference list.
        $brands = array_filter(array_map('trim', explode(',', $input['brands'])));
        
        // If at least one brand is specified, add them to the query.
        if(count($brands) > 0)
        {
            $query->join('UserBrands b', 'b.userId = u.userId', 'left')
                  ->where_in('brandName', $brands);
        }
        
              
        // Retrieve personality preference.
        $personPref = array($input['attitude'],
                            $input['perceiving'],
                            $input['judging'],
                            $input['lifestyle']
                       );
        
        $dicts1 = 'INTJ';
        $dicts2 = 'ESFP';
        for($i = 0; $i < 4; ++$i)
        {
            $col = 'preference' . $dicts1[$i];
            if($personPref[$i] == $dicts1[$i])
            {
                $query->where("$col >= 0.5");
            }
            else
            {
                $query->where("$col < 0.5");
            }
        }
        
        // Execute query.
        $result = $query->get()->result_array();
        
        // Return userId's.
        foreach($result as &$row)
        {
            $row = $row['userId'];
        }
        return array_merge($result);
    }
}<file_sep><?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 

class Authentication
{
    /**
     * @return bool Whether somebody is logged in.
     */
    public function userLoggedIn()
    {
        return $this->currentUserId() !== null;
    }
    
    /**
     * @return int The ID of the user that is currently logged in, or null of none is.
     */
    public function currentUserId()
    {       
        // Get CodeIgniter object.
        $ci =& get_instance();
        
        // Fetch id from session data.
        // If the session ID in the user's cookie does not match a session in the database, 
        // CodeIgniter will have destroyed the session already and this will return false.
        $id = $ci->session->userdata('userId');
        
        if($id !== false)
        {
            return $id;
        }
        else
        {
            return null;
        }
    }
    
    public function currentUserName()
    {
        $ci =& get_instance();
        
        $ci->load->model('User','user');
        
        $id = $this->currentUserId();
        
        if($id === null)
        {
            return null;
        }
        else
        {
            $result = $ci->user->load($id);
            return $result['username'];
        }
    }
    
    public function userIsAdmin()
    {
    	// Fetch current user.
        $id = $this->currentUserId();
        
        if($id !== null)
        {
            // Get CodeIgniter object.
            $ci =& get_instance();
            
            // Confirm whether admin with database.
            $result = $ci->db->select('admin')->from('Users')
                             ->where('userId', $id)->get()->row();
            
            if($result->admin)
            {
                // If an admin, return true.
                return true;
            }
        }
        
        // No admin.
        return false;
    }
    
    /**
     * Throws an exception if the currently logged in user, if any, is not an administrator.
     * 
     * This should be used to protect admin only functionality at the server side against forged
     * requests. This function shouldn't be used in areas reachable by regular users.
     * 
     * @throws Exception If no administrator is logged in.
     */
    public function assertAdministrator()
    {
        if(!$this->userIsAdmin())
        {
        	throw new Exception('Access denied.');
        }
    }
    
    
    /**
    * Logs in a user.
    *
    * @param email     The e-mail adres the user has entered.
    * @param password  The <PASSWORD> the user has entered.
    * 
    * @return int    The user ID of the user that has been succesfully logged in, if any. 
    *                If no user with the provided e-mail/password combination exists. This will
    *                return null.
    */
    public function login($email, $password)
    {
        // Get CodeIgniter object.
        $ci =& get_instance();
        
        if($this->userLoggedIn())
        {
            // You can't log in twice.
            return null;
        }
    
        // Load user model.
        $ci->load->model('user');
    
        // Look up which user belongs to this e-mail address and password.
        $id = $ci->user->lookup($email, $password);
    
    
        if($id === null)
        {
            // Login failed. Return null.
            return null;
        }
        else
        {
            // Add the user's ID to the session data.
            $ci->session->set_userdata('userId', $id);

            // Return the username.
            return $id;
        }
    }
    
    /**
     * Logs out the current user.
     */
    public function logout()
    {
        // Simply destroy the current session.
        $ci =& get_instance();
        $ci->session->set_userdata('userId', null);
        $ci->session->sess_destroy();
    }
}
